use super::Action;
use crate::config;
use crate::{Error, PlaceholderMap, Result};
use async_trait::async_trait;
pub struct Email {
from: lettre::message::Mailbox,
to: lettre::message::Mailbox,
reply_to: Option<lettre::message::Mailbox>,
subject: String,
body: String,
smtp_server: String,
smtp_port: Option<u16>,
smtp_security: config::SmtpSecurity,
username: String,
password: String,
}
impl TryFrom<&config::Action> for Email {
type Error = Error;
fn try_from(action: &config::Action) -> std::result::Result<Self, Self::Error> {
if let config::ActionType::Email(email) = &action.type_ {
if email.subject.is_empty() {
Err(Error(String::from("'subject' cannot be empty.")))
} else if email.body.is_empty() {
Err(Error(String::from("'body' cannot be empty.")))
} else if email.smtp_server.is_empty() {
Err(Error(String::from("'smtp_server' cannot be empty.")))
} else if email.username.is_empty() {
Err(Error(String::from("'username' cannot be empty.")))
} else if email.password.is_empty() {
Err(Error(String::from("'password' cannot be empty.")))
} else {
Ok(Self {
from: email
.from
.parse()
.map_err(|x| Error(format!("Invalid sender email address: {x}")))?,
to: email
.to
.parse()
.map_err(|x| Error(format!("Invalid recipient email address: {x}")))?,
reply_to: email.reply_to.as_ref().map_or(Ok(None), |x| {
Ok(Some(x.parse().map_err(|x| {
Error(format!("Invalid reply-to email address: {x}"))
})?))
})?,
subject: email.subject.clone(),
body: email.body.clone(),
smtp_server: email.smtp_server.clone(),
smtp_port: email.smtp_port,
smtp_security: email.smtp_security,
username: email.username.clone(),
password: email.password.clone(),
})
}
} else {
panic!();
}
}
}
#[async_trait]
impl Action for Email {
async fn trigger(&self, placeholders: PlaceholderMap) -> Result<()> {
use lettre::transport::smtp::authentication::Credentials;
use lettre::{AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor};
let subject = crate::fill_placeholders(self.subject.as_str(), &placeholders);
let body = crate::fill_placeholders(self.body.as_str(), &placeholders);
let mut message_builder = Message::builder()
.from(self.from.clone())
.to(self.to.clone())
.subject(&subject)
.user_agent(crate::user_agent());
if let Some(reply_to) = &self.reply_to {
message_builder = message_builder.reply_to(reply_to.clone());
}
let email = message_builder
.body(body)
.map_err(|x| Error(x.to_string()))?;
let credentials = Credentials::new(self.username.clone(), self.password.clone());
let mut mailer_builder = match self.smtp_security {
config::SmtpSecurity::TLS => {
AsyncSmtpTransport::<Tokio1Executor>::relay(&self.smtp_server)
.map_err(|x| Error(x.to_string()))
}
config::SmtpSecurity::STARTTLS => {
AsyncSmtpTransport::<Tokio1Executor>::starttls_relay(&self.smtp_server)
.map_err(|x| Error(x.to_string()))
}
config::SmtpSecurity::Plain => Ok(
AsyncSmtpTransport::<Tokio1Executor>::builder_dangerous(&self.smtp_server),
),
}?;
mailer_builder = mailer_builder.credentials(credentials);
if let Some(port) = self.smtp_port {
mailer_builder = mailer_builder.port(port);
}
let mailer = mailer_builder.build();
mailer
.send(email)
.await
.map_err(|x| Error(format!("Failed to send email: {x}")))?;
Ok(())
}
}