From 1ab6ecba6f509b7b76865d65c77ecebc51efd2d3 Mon Sep 17 00:00:00 2001 From: sommerfeld Date: Sun, 21 Apr 2024 16:04:38 +0100 Subject: Initial commit --- src/actions/email.rs | 142 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 src/actions/email.rs (limited to 'src/actions/email.rs') diff --git a/src/actions/email.rs b/src/actions/email.rs new file mode 100644 index 0000000..272f650 --- /dev/null +++ b/src/actions/email.rs @@ -0,0 +1,142 @@ +use super::Action; +use crate::message::MessageConfig; +use crate::message::MessageFormat; +use crate::message::MessageParams; +use anyhow::{Context, Result}; +use async_trait::async_trait; +use lettre::message::header::ContentType; +use lettre::message::MessageBuilder; +use lettre::message::MultiPart; +use lettre::message::SinglePart; +use lettre::transport::smtp::authentication::Credentials; +use lettre::transport::smtp::client::Tls; +use lettre::transport::smtp::client::TlsParametersBuilder; +use lettre::AsyncSmtpTransport; +use lettre::AsyncTransport; +use lettre::Message; +use lettre::Tokio1Executor; +use serde::Deserialize; + +#[derive(Deserialize, Debug, Copy, Clone)] +#[serde(rename_all = "lowercase")] +pub enum EmailConnectionType { + Plain, + StartTls, + Tls, +} + +#[derive(Deserialize, Debug)] +pub struct EmailConfig { + server: String, + port: Option, + credentials: Option, + connection: Option, + self_signed_cert: Option, + from: String, + to: Option, +} + +impl EmailConfig { + pub fn server(&self) -> &str { + &self.server + } + + pub fn connection(&self) -> EmailConnectionType { + self.connection.unwrap_or(EmailConnectionType::Tls) + } + + pub fn self_signed_cert(&self) -> bool { + self.self_signed_cert.unwrap_or(false) + } + + pub fn port(&self) -> u16 { + self.port.unwrap_or(match self.connection() { + EmailConnectionType::Tls => 587, + EmailConnectionType::StartTls => 465, + EmailConnectionType::Plain => 25, + }) + } + + pub fn to(&self) -> &str { + self.to.as_deref().unwrap_or(self.from.as_ref()) + } +} + +pub struct EmailAction<'a> { + message_config: &'a MessageConfig, + mailer: AsyncSmtpTransport, + message_builder: MessageBuilder, +} +impl<'a> EmailAction<'a> { + pub fn new(message_config: &'a MessageConfig, email_config: &'a EmailConfig) -> Result { + let tls_builder = TlsParametersBuilder::new(email_config.server().into()) + .dangerous_accept_invalid_certs(email_config.self_signed_cert()); + let tls_parameters = tls_builder.build()?; + + let mut smtp_builder = + AsyncSmtpTransport::::builder_dangerous(email_config.server()) + .port(email_config.port()) + .tls(match email_config.connection() { + EmailConnectionType::Tls => Tls::Wrapper(tls_parameters), + EmailConnectionType::StartTls => Tls::Required(tls_parameters), + EmailConnectionType::Plain => Tls::None, + }); + if let Some(cred) = &email_config.credentials { + smtp_builder = smtp_builder.credentials(cred.clone()) + } + Ok(Self { + message_config, + mailer: smtp_builder.build(), + message_builder: Message::builder() + .from( + email_config + .from + .parse() + .with_context(|| format!("invalid from address '{}'", email_config.from))?, + ) + .to(email_config + .to() + .parse() + .with_context(|| format!("invalid to address '{}'", email_config.to()))?), + }) + } +} + +#[async_trait] +impl Action<'_> for EmailAction<'_> { + async fn run(&self, params: Option<&MessageParams<'_, '_>>) -> Result<()> { + let body = self.message_config.body(params)?; + let html_body = match self.message_config.format() { + MessageFormat::Markdown => format!( + "{}", + markdown::to_html(&body) + ), + MessageFormat::Html => body.clone(), + MessageFormat::Plain => Default::default(), + }; + let email_builder = self + .message_builder + .clone() + .subject(self.message_config.subject(params)?); + let email = match self.message_config.format() { + MessageFormat::Plain => email_builder + .header(ContentType::TEXT_PLAIN) + .body(body.clone())?, + MessageFormat::Markdown | MessageFormat::Html => email_builder.multipart( + MultiPart::alternative() + .singlepart( + SinglePart::builder() + .header(ContentType::TEXT_PLAIN) + .body(body.clone()), + ) + .singlepart( + SinglePart::builder() + .header(ContentType::TEXT_HTML) + .body(html_body.clone()), + ), + )?, + }; + self.mailer.send(email).await?; + Ok(()) + } +} -- cgit v1.2.3-70-g09d2