a06673b
use std::time::Duration;
use lettre::AsyncTransport;
use lettre::Tokio1Executor;
use lettre::message::Mailbox;
use lettre::message::Message;
use lettre::transport::smtp::AsyncSmtpTransport;
use lettre::transport::smtp::authentication::Credentials;
use lettre::transport::smtp::client::{Tls, TlsParameters};
use crate::config::AppEmailSettings;
use crate::error::EmailError;
pub struct EmailTransport {
inner: AsyncSmtpTransport<Tokio1Executor>,
from: Mailbox,
}
impl EmailTransport {
pub fn from_config(cfg: &AppEmailSettings) -> Result<Self, EmailError> {
cfg.is_valid().map_err(EmailError::Config)?;
let port = cfg.smtp_port;
let resp_timeout = Some(Duration::from_millis(cfg.response_timeout_ms));
let host = cfg.smtp_host.clone();
let builder = if port == 465 {
AsyncSmtpTransport::<Tokio1Executor>::builder_dangerous(host.clone())
.port(port)
.timeout(resp_timeout)
.tls(Tls::Wrapper(TlsParameters::new(host.clone())?))
} else if port == 587 {
AsyncSmtpTransport::<Tokio1Executor>::relay(&host)?
.port(port)
.timeout(resp_timeout)
} else {
AsyncSmtpTransport::<Tokio1Executor>::builder_dangerous(host)
.port(port)
.timeout(resp_timeout)
};
let transport = if let (Some(u), Some(p)) = (&cfg.smtp_username, &cfg.smtp_password) {
builder
.credentials(Credentials::new(u.clone(), p.clone()))
.build()
} else {
builder.build()
};
let from = Mailbox::new(cfg.from_name.clone(), cfg.from_address.clone().parse()?);
Ok(Self {
inner: transport,
from,
})
}
pub async fn send(&self, message: Message) -> Result<String, EmailError> {
let response = self.inner.send(message).await?;
let text = response.message().collect::<Vec<_>>().join(" ");
Ok(text)
}
pub fn from_mailbox(&self) -> Mailbox {
self.from.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn base_cfg() -> AppEmailSettings {
AppEmailSettings {
enabled: true,
smtp_host: "smtp.example.com".into(),
smtp_port: 587,
..Default::default()
}
}
#[test]
fn build_transport_with_default_config() {
let transport = EmailTransport::from_config(&base_cfg()).unwrap();
assert_eq!(transport.from.email.to_string(), "no-reply@example.com");
}
#[test]
fn build_transport_with_credentials() {
let cfg = AppEmailSettings {
smtp_username: Some("user".into()),
smtp_password: Some("pw".into()),
..base_cfg()
};
let _transport = EmailTransport::from_config(&cfg).unwrap();
}
#[test]
fn build_transport_implicit_tls_465() {
let cfg = AppEmailSettings {
smtp_port: 465,
..base_cfg()
};
let _transport = EmailTransport::from_config(&cfg).unwrap();
}
#[test]
fn build_transport_plain_25() {
let cfg = AppEmailSettings {
smtp_port: 25,
..base_cfg()
};
let _transport = EmailTransport::from_config(&cfg).unwrap();
}
#[test]
fn invalid_config_rejected() {
let cfg = AppEmailSettings {
enabled: true,
smtp_host: "".into(),
..Default::default()
};
assert!(EmailTransport::from_config(&cfg).is_err());
}
}