pr_01m47d15m3e54sn21z27rpy5n9/services/identity/src/email.rs

72 lines2,384 bytesCodeBlame
1//! Transactional email through Cloudflare Email Sending.
2
3use g1t_kit::js;
4use serde::Serialize;
5use worker::{Env, Result};
6
7const FROM: &str = "g1t <noreply@g1t.sh>";
8const SITE: &str = "https://g1t.sh";
9
10#[derive(Serialize)]
11struct Message<'a> {
12 to: &'a str,
13 from: &'a str,
14 subject: &'a str,
15 text: String,
16 html: String,
17}
18
19/// A short message with one link to follow.
20async fn send_link(
21 env: &Env,
22 to: &str,
23 subject: &str,
24 intro: &str,
25 action: &str,
26 link: &str,
27 footer: &str,
28) -> Result<()> {
29 let message = Message {
30 to,
31 from: FROM,
32 subject,
33 text: format!("{intro}\n\n{action}: {link}\n\n{footer}\n"),
34 html: format!(
35 "<div style=\"font-family:system-ui,sans-serif;max-width:480px;margin:0 auto;padding:32px 16px;color:#16150f\">\
36 <p style=\"font-size:20px;font-weight:600;margin:0 0 16px\">g1t</p>\
37 <p style=\"font-size:15px;line-height:1.6\">{intro}</p>\
38 <p style=\"margin:24px 0\"><a href=\"{link}\" style=\"background:#16150f;color:#fff;text-decoration:none;padding:10px 18px;border-radius:6px;font-size:15px\">{action}</a></p>\
39 <p style=\"font-size:13px;line-height:1.6;color:#6e6a5e\">{footer}</p>\
40 </div>"
41 ),
42 };
43 let binding = js::binding(env, "EMAIL")?;
44 js::call(&binding, "send", &[js::to_js(&message)?]).await?;
45 Ok(())
46}
47
48pub async fn send_verification(env: &Env, to: &str, username: &str, token: &str) -> Result<()> {
49 send_link(
50 env,
51 to,
52 "Confirm your email for g1t",
53 &format!("Welcome to g1t, {username}. Confirm this address to finish creating your account."),
54 "Confirm email",
55 &format!("{SITE}/verify?token={token}"),
56 "This link works for 24 hours. If you did not create a g1t account, you can ignore this message.",
57 )
58 .await
59}
60
61pub async fn send_password_reset(env: &Env, to: &str, username: &str, token: &str) -> Result<()> {
62 send_link(
63 env,
64 to,
65 "Reset your g1t password",
66 &format!("Someone asked to reset the password for the g1t account {username}."),
67 "Choose a new password",
68 &format!("{SITE}/reset?token={token}"),
69 "This link works for 1 hour. If this was not you, ignore this message and your password stays the same.",
70 )
71 .await
72}