use lettre::{ AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor, message::Mailbox, transport::smtp::{authentication::Credentials, extension::ClientId}, }; use crate::{ config::AppConfig, error::{AppError, AppResult}, }; fn transport(config: &AppConfig) -> AppResult> { let smtp = &config.smtp; let mut builder = match smtp.kind.as_str() { "starttls" => AsyncSmtpTransport::::starttls_relay(&smtp.host) .map_err(|e| AppError::Mail(e.to_string()))?, "tls" => AsyncSmtpTransport::::relay(&smtp.host) .map_err(|e| AppError::Mail(e.to_string()))?, _ => AsyncSmtpTransport::::builder_dangerous(&smtp.host), } .port(smtp.port); if !smtp.client_name.trim().is_empty() { builder = builder.hello_name(ClientId::Domain(smtp.client_name.trim().to_string())); } if !smtp.username.is_empty() { builder = builder.credentials(Credentials::new( smtp.username.clone(), smtp.password.clone(), )); } Ok(builder.build()) } pub fn sender_named( config: &AppConfig, fallback: &str, display_name: Option<&str>, ) -> AppResult { let address = if config.smtp.admin_email.is_empty() { fallback } else { &config.smtp.admin_email }; let parsed = address .parse() .map_err(|e| AppError::bad_request(format!("invalid sender address: {e}")))?; let name = display_name .filter(|value| !value.trim().is_empty()) .unwrap_or(&config.smtp.admin_name); Ok(Mailbox::new(Some(name.to_string()), parsed)) } pub async fn send_text( config: &AppConfig, from_fallback: &str, to: &str, subject: &str, body: &str, ) -> AppResult<()> { send_text_named(config, from_fallback, None, to, subject, body).await } pub async fn send_text_named( config: &AppConfig, from_fallback: &str, from_name: Option<&str>, to: &str, subject: &str, body: &str, ) -> AppResult<()> { let to: Mailbox = to .parse() .map_err(|e| AppError::bad_request(format!("invalid recipient: {e}")))?; let message = Message::builder() .from(sender_named(config, from_fallback, from_name)?) .to(to) .subject(subject) .body(body.to_owned()) .map_err(|e| AppError::Mail(e.to_string()))?; transport(config)? .send(message) .await .map_err(|e| AppError::Mail(e.to_string()))?; Ok(()) }