gitdataai/libs/email/lib.rs
2026-04-15 09:08:09 +08:00

78 lines
2.4 KiB
Rust

use config::AppConfig;
use lettre::message::Mailbox;
use lettre::transport::smtp::authentication::Credentials;
use lettre::transport::smtp::client::Tls;
use lettre::{SmtpTransport, Transport};
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::sync::LazyLock;
use std::time::Duration;
#[derive(Clone)]
pub struct AppEmail {
pub cred: Credentials,
pub mailer: SmtpTransport,
pub from: Mailbox,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct EmailMessage {
pub to: String,
pub subject: String,
pub body: String,
}
static EMAIL_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap());
impl AppEmail {
pub async fn init(cfg: &AppConfig) -> anyhow::Result<Self> {
let smtp_host = cfg.smtp_host()?;
let smtp_port = cfg.smtp_port()?;
let smtp_username = cfg.smtp_username()?;
let smtp_password = cfg.smtp_password()?;
let smtp_from = cfg.smtp_from()?;
let smtp_tls = cfg.smtp_tls()?;
let smtp_timeout = cfg.smtp_timeout()?;
let cred = Credentials::new(smtp_username, smtp_password);
let tls_param = if smtp_tls {
Tls::Required(
lettre::transport::smtp::client::TlsParameters::builder(smtp_host.clone())
.build()
.map_err(|e| anyhow::anyhow!("Failed to build TLS parameters: {}", e))?,
)
} else {
Tls::None
};
let mailer = SmtpTransport::builder_dangerous(smtp_host)
.port(smtp_port)
.tls(tls_param)
.timeout(Some(Duration::from_secs(smtp_timeout)))
.credentials(cred.clone())
.build();
Ok(AppEmail {
cred,
mailer,
from: smtp_from
.parse()
.map_err(|e| anyhow::anyhow!("Invalid from address: {}", e))?,
})
}
pub async fn send(&self, msg: EmailMessage) -> anyhow::Result<()> {
if !EMAIL_REGEX.is_match(&msg.to) {
return Err(anyhow::anyhow!("Invalid email address format: {}", msg.to));
}
let email = lettre::Message::builder()
.from(self.from.clone())
.to(msg.to.parse()?)
.subject(msg.subject)
.body(msg.body)?;
self.mailer
.send(&email)
.map_err(|e| anyhow::anyhow!("{}", e))?;
Ok(())
}
}