use std::sync::Arc; use lettre::{ AsyncTransport, Message, Tokio1Executor, transport::smtp::{AsyncSmtpTransport, authentication::Credentials}, }; use crate::consts; #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Parse error: {0}")] Parse(#[from] lettre::address::AddressError), #[error("SMTP error: {0}")] Smtp(#[from] lettre::transport::smtp::Error), #[error("Email error: {0}")] Email(#[from] lettre::error::Error), } #[async_trait::async_trait] pub trait EmailServiceTrait: Send + Sync { async fn send_email( &self, email_sender: &str, email_receiver: &str, title: &str, message: &str, ) -> Result<(), Error>; } pub struct EmailService { smtp_relay_address: String, smtp_login: String, smtp_password: String, } impl EmailService { pub fn create_service( smtp_relay_address: &str, smtp_login: &str, smtp_password: &str, ) -> Arc { Arc::new(Self { smtp_relay_address: smtp_relay_address.to_string(), smtp_login: smtp_login.to_string(), smtp_password: smtp_password.to_string(), }) } } #[async_trait::async_trait] impl EmailServiceTrait for EmailService { /// A function to send an email using the given SMTP address. /// It may timeout if the SMTP server is not reachable, see [consts::SEND_EMAIL_TIMEOUT]. async fn send_email( &self, email_sender: &str, email_receiver: &str, title: &str, message: &str, ) -> Result<(), Error> { let email = Message::builder() .message_id(None) .from(email_sender.parse()?) .to(email_receiver.parse()?) .subject(title) .body(message.to_string())?; let credentials = Credentials::new(self.smtp_login.clone(), self.smtp_password.clone()); let mailer = AsyncSmtpTransport::::relay(&self.smtp_relay_address)? .credentials(credentials) .timeout(Some(consts::SEND_EMAIL_TIMEOUT)) .build(); mailer.send(email).await?; Ok(()) } }