recipes/backend/src/email.rs
2025-05-07 20:12:49 +02:00

82 lines
2.1 KiB
Rust

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<dyn EmailServiceTrait> {
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::<Tokio1Executor>::relay(&self.smtp_relay_address)?
.credentials(credentials)
.timeout(Some(consts::SEND_EMAIL_TIMEOUT))
.build();
mailer.send(email).await?;
Ok(())
}
}