//! Authentication and account action email templates. use crate::db::ListKind; use crate::email::{Audience, Delivery, EmailClass, EmailClient, OperationalKind}; use crate::error::Result; impl EmailClient { /// Send a password reset email pub async fn send_password_reset( &self, to_email: &str, to_name: Option<&str>, reset_url: &str, ) -> Result<()> { let subject = "Reset your password"; let body = format!( r"Hi{name}, You requested to reset your password. Click the link below to set a new password: {url} This link expires in 15 minutes. If you didn't request this, you can ignore this email. - Makenotwork", name = crate::email::greeting(to_name), url = reset_url ); self.dispatch( EmailClass::Operational(OperationalKind::PasswordReset), Audience::Address(to_email), subject, &body, Delivery::default(), ) .await } /// Send an email verification email pub async fn send_verification( &self, to_email: &str, to_name: Option<&str>, verify_url: &str, ) -> Result<()> { let subject = "Verify your email"; let body = format!( r"Hi{name}, Please verify your email address by clicking the link below: {url} This link expires in 24 hours. - Makenotwork", name = crate::email::greeting(to_name), url = verify_url ); self.dispatch( EmailClass::Operational(OperationalKind::EmailVerification), Audience::Address(to_email), subject, &body, Delivery::default(), ) .await } /// Sent when someone tries to sign up with an email that already has an /// account. The signup page deliberately does not reveal that the address is /// registered (enumeration defense); this email is the channel that reaches /// the real owner so they can recover, since only they receive it. pub async fn send_account_exists( &self, to_email: &str, login_url: &str, reset_url: &str, ) -> Result<()> { let subject = "You already have a Makenotwork account"; let body = format!( r"Hi, Someone (probably you) just tried to sign up for Makenotwork with this email address, but you already have an account. To sign in: {login_url} If you've forgotten your password, you can reset it here: {reset_url} If this wasn't you, nothing was created or changed and you can safely ignore this email. - Makenotwork", ); self.dispatch( EmailClass::Operational(OperationalKind::AccountExists), Audience::Address(to_email), subject, &body, Delivery::default(), ) .await } /// Send an account lockout notification pub async fn send_lockout_notification( &self, to_email: &str, to_name: Option<&str>, login_link_url: Option<&str>, ) -> Result<()> { let subject = "Security alert: Account locked"; let body = format!( r"Hi{name}, Your account has been temporarily locked due to multiple failed login attempts. {login_link} If this wasn't you, please contact support immediately. - Makenotwork", name = crate::email::greeting(to_name), login_link = login_link_url.map_or_else( || "Your account will unlock automatically in 15 minutes.".to_string(), |url| format!( "Use this link to log in securely:\n\n{url}\n\nThis link expires in 15 minutes." ) ) ); self.dispatch( EmailClass::Operational(OperationalKind::AccountLockout), Audience::Address(to_email), subject, &body, Delivery::default(), ) .await } /// Send a one-time login link pub async fn send_login_link( &self, to_email: &str, to_name: Option<&str>, login_url: &str, ) -> Result<()> { let subject = "Your login link"; let body = format!( r"Hi{name}, Click the link below to log in to your account: {url} This link expires in 15 minutes and can only be used once. If you didn't request this, you can ignore this email. - Makenotwork", name = crate::email::greeting(to_name), url = login_url ); self.dispatch( EmailClass::Operational(OperationalKind::LoginLink), Audience::Address(to_email), subject, &body, Delivery::default(), ) .await } /// Send account deletion confirmation email pub async fn send_deletion_confirmation( &self, to_email: &str, to_name: Option<&str>, delete_url: &str, ) -> Result<()> { let subject = "Confirm account deletion"; let body = format!( r"Hi{name}, You requested to delete your Makenotwork account. IMPORTANT: This action is permanent and cannot be undone. Clicking the link below will immediately and permanently delete: - All your projects and items - All uploaded content (audio, images) - Your profile and account settings - Your custom links Purchases made by your fans will remain accessible to them. To confirm deletion, click this link: {url} This link expires in 1 hour. If you did not request this, do NOT click the link. Your account is safe. - Makenotwork", name = crate::email::greeting(to_name), url = delete_url ); self.dispatch( EmailClass::Operational(OperationalKind::DeletionConfirmation), Audience::Address(to_email), subject, &body, Delivery::default(), ) .await } /// Send a new-device login notification pub async fn send_new_login_notification( &self, to_user_id: crate::db::UserId, to_email: &str, to_name: Option<&str>, device: Option<&str>, ip: Option<&str>, unsub_url: Option<&str>, ) -> Result<()> { let subject = "New sign-in to your account"; let device_line = device.unwrap_or("Unknown device"); let ip_line = ip.unwrap_or("Unknown"); let body = format!( r"Hi{name}, Your account was just signed in to from a new device. Device: {device} IP address: {ip} If this was you, no action is needed. If you don't recognize this sign-in, go to your dashboard and revoke the session under Settings > Sessions. - Makenotwork", name = crate::email::greeting(to_name), device = device_line, ip = ip_line, ); self.dispatch( EmailClass::Optional(ListKind::Login), Audience::User(to_user_id, to_email), subject, &body, Delivery { unsub_url, ..Default::default() }, ) .await } }