use lettre::message::Mailbox; use lettre::message::header::ContentType; use lettre::transport::smtp::authentication::Credentials; use lettre::transport::smtp::client::{Tls, TlsParameters}; use lettre::{Message, SmtpTransport, Transport}; use thiserror::Error; use tracing::warn; #[derive(Error, Debug)] pub enum MailError { #[error("Environment Error")] Env(#[from] dotenvy::Error), #[error("Mail Error: {0}")] Mail(#[from] lettre::error::Error), #[error("Address Parse Error: {0}")] Address(#[from] lettre::address::AddressError), #[error("SMTP Error: {0}")] Smtp(#[from] lettre::transport::smtp::Error), } pub fn send_email_raw( to: String, subject: String, body: String, ) -> Result<(), MailError> { let from_name = dotenvy::var("SMTP_FROM_NAME") .unwrap_or_else(|_| "Modrinth".to_string()); let from_address = dotenvy::var("SMTP_FROM_ADDRESS") .unwrap_or_else(|_| "no-reply@mail.modrinth.com".to_string()); let email = Message::builder() .from(Mailbox::new(Some(from_name), from_address.parse()?)) .to(to.parse()?) .subject(subject) .header(ContentType::TEXT_HTML) .body(body)?; let username = dotenvy::var("SMTP_USERNAME")?; let password = dotenvy::var("SMTP_PASSWORD")?; let host = dotenvy::var("SMTP_HOST")?; let port = dotenvy::var("SMTP_PORT")?.parse::().unwrap_or(465); let creds = (!username.is_empty()).then(|| Credentials::new(username, password)); let tls_setting = match dotenvy::var("SMTP_TLS")?.as_str() { "none" => Tls::None, "opportunistic_start_tls" => { Tls::Opportunistic(TlsParameters::new(host.to_string())?) } "requires_start_tls" => { Tls::Required(TlsParameters::new(host.to_string())?) } "tls" => Tls::Wrapper(TlsParameters::new(host.to_string())?), _ => { warn!("Unrecognized SMTP TLS setting. Defaulting to TLS."); Tls::Wrapper(TlsParameters::new(host.to_string())?) } }; let mut mailer = SmtpTransport::relay(&host)?.port(port).tls(tls_setting); if let Some(creds) = creds { mailer = mailer.credentials(creds); } mailer.build().send(&email)?; Ok(()) } pub fn send_email( to: String, email_title: &str, email_description: &str, line_two: &str, button_info: Option<(&str, &str)>, ) -> Result<(), MailError> { let mut email = if button_info.is_some() { include_str!("button_notif.html") } else { include_str!("auth_notif.html") } .replace("{{ email_title }}", email_title) .replace("{{ email_description }}", email_description) .replace("{{ line_one }}", email_description) .replace("{{ line_two }}", line_two); if let Some((button_title, button_link)) = button_info { email = email .replace("{{ button_title }}", button_title) .replace("{{ button_link }}", button_link); } send_email_raw(to, email_title.to_string(), email)?; Ok(()) }