🔥 Okibi

yuki / okibi

焚き火の仲間だけの自前Gitホスティング(このサイト自身)

git clone https://okibi.fly.dev/yuki/okibi.git

okibi / src / email.rs

use crate::config::Config;

/// Send a magic-link email via Resend. If no key is configured, log the link
/// to stdout (dev mode) so local flows still work.
pub async fn send_magic_link(cfg: &Config, to: &str, link: &str) {
    if cfg.resend_key.is_none() {
        tracing::warn!("[dev] magic link for {to}: {link}");
        println!("\n🔥 [dev] Okibi magic link for {to}:\n   {link}\n");
        return;
    }
    let key = cfg.resend_key.as_ref().unwrap();
    let html = format!(
        r#"<div style="font-family:system-ui,sans-serif;max-width:480px;margin:0 auto">
        <h2>🔥 Okibi にログイン</h2>
        <p>焚き火の仲間だけの Git。下のボタンでログインします(15分有効)。</p>
        <p><a href="{link}" style="display:inline-block;background:#e25822;color:#fff;
        padding:12px 20px;border-radius:8px;text-decoration:none">ログイン</a></p>
        <p style="color:#888;font-size:12px">心当たりがなければ無視してください。</p></div>"#
    );
    let body = serde_json::json!({
        "from": cfg.mail_from,
        "to": [to],
        "subject": "🔥 Okibi ログインリンク",
        "html": html,
    });
    let client = reqwest::Client::new();
    match client
        .post("https://api.resend.com/emails")
        .bearer_auth(key)
        .json(&body)
        .send()
        .await
    {
        Ok(r) if r.status().is_success() => tracing::info!("magic link emailed to {to}"),
        Ok(r) => tracing::error!("resend failed {}: {}", r.status(), r.text().await.unwrap_or_default()),
        Err(e) => tracing::error!("resend error: {e}"),
    }
}