use std::time::Duration; use lettre::{ message::Mailbox, transport::smtp::{ authentication::Credentials, client::{Tls, TlsParameters}, }, AsyncSmtpTransport, Message, Tokio1Executor, }; use rocket::http::uri::Absolute; use serde_json::json; use crate::types::UserID; pub struct Mailer { from: Mailbox, verify_base: Absolute<'static>, transport: AsyncSmtpTransport, } impl Mailer { pub fn new( from: Mailbox, host: &str, port: u16, starttls: bool, credentials: Option, verify_base: Absolute<'static>, ) -> anyhow::Result { let transport = AsyncSmtpTransport::::builder_dangerous(host) .port(port) .tls(if starttls { Tls::Required(TlsParameters::new(host.to_string())?) } else { Tls::None }) .timeout(Some(Duration::from_secs(5))); Ok(Mailer { from, verify_base, transport: match credentials { None => transport.build(), Some(c) => transport.credentials(c).build(), }, }) } pub(crate) async fn send_account_verify( &self, uid: &UserID, to: &str, code: &str, ) -> anyhow::Result<()> { let fragment = base64::encode_config( serde_json::to_string(&json!({ "uid": uid, "email": to, "code": code, }))?, base64::URL_SAFE, ); let email = Message::builder() .from(self.from.clone()) .to(to.parse()?) .subject("account verify code") .body(format!("{}/#/verify/{fragment}", self.verify_base))?; lettre::AsyncTransport::send(&self.transport, email).await?; Ok(()) } pub(crate) async fn send_session_verify(&self, to: &str, code: &str) -> anyhow::Result<()> { let email = Message::builder() .from(self.from.clone()) .to(to.parse()?) .subject("session verify code") .body(code.to_string())?; lettre::AsyncTransport::send(&self.transport, email).await?; Ok(()) } pub(crate) async fn send_password_changed(&self, to: &str) -> anyhow::Result<()> { let email = Message::builder() .from(self.from.clone()) .to(to.parse()?) .subject("account password has been changed") .body(String::from( "your account password has been changed. if you haven't done this, \ you're probably in trouble now.", ))?; lettre::AsyncTransport::send(&self.transport, email).await?; Ok(()) } pub(crate) async fn send_password_forgot(&self, to: &str, code: &str) -> anyhow::Result<()> { let email = Message::builder() .from(self.from.clone()) .to(to.parse()?) .subject("account reset code") .body(code.to_string())?; lettre::AsyncTransport::send(&self.transport, email).await?; Ok(()) } pub(crate) async fn send_account_reset(&self, to: &str) -> anyhow::Result<()> { let email = Message::builder() .from(self.from.clone()) .to(to.parse()?) .subject("account has been reset") .body(String::from( "your account has been reset. if you haven't done this, \ you're probably in trouble now.", ))?; lettre::AsyncTransport::send(&self.transport, email).await?; Ok(()) } }