use lettre::{
transport::smtp::{authentication::Credentials, client::Tls},
SmtpTransport,
};
use crate::{Error, Result, Success};
#[derive(Serialize, Deserialize, Clone)]
pub struct SMTPSettings {
pub from: String,
pub reply_to: Option<String>,
pub host: String,
pub port: Option<i32>,
pub username: String,
pub password: String,
pub use_tls: Option<bool>,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct Template {
pub title: String,
pub text: String,
pub html: Option<String>,
pub url: String,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct Templates {
pub verify: Template,
pub reset: Template,
pub deletion: Template,
pub welcome: Option<Template>,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct EmailExpiryConfig {
pub expire_verification: i64,
pub expire_password_reset: i64,
pub expire_account_deletion: i64,
}
impl Default for EmailExpiryConfig {
fn default() -> EmailExpiryConfig {
EmailExpiryConfig {
expire_verification: 3600 * 24,
expire_password_reset: 3600,
expire_account_deletion: 3600,
}
}
}
#[derive(Serialize, Deserialize, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum EmailVerificationConfig {
Disabled,
Enabled {
smtp: SMTPSettings,
templates: Templates,
expiry: EmailExpiryConfig,
},
}
impl Default for EmailVerificationConfig {
fn default() -> EmailVerificationConfig {
EmailVerificationConfig::Disabled
}
}
impl SMTPSettings {
pub fn create_transport(&self) -> SmtpTransport {
let relay = SmtpTransport::relay(&self.host).unwrap();
let relay = if let Some(port) = self.port {
relay.port(port.try_into().unwrap())
} else {
relay
};
let relay = if let Some(false) = self.use_tls {
relay.tls(Tls::None)
} else {
relay
};
relay
.credentials(Credentials::new(
self.username.clone(),
self.password.clone(),
))
.build()
}
fn render_template(text: &str, variables: &handlebars::JsonValue) -> Result<String> {
lazy_static! {
static ref HANDLEBARS: handlebars::Handlebars<'static> = handlebars::Handlebars::new();
}
HANDLEBARS
.render_template(text, variables)
.map_err(|_| Error::RenderFail)
}
pub fn send_email(
&self,
address: String,
template: &Template,
variables: handlebars::JsonValue,
) -> Success {
let m = lettre::Message::builder()
.from(self.from.parse().expect("valid `smtp_from`"))
.to(address.parse().expect("valid `smtp_to`"))
.subject(template.title.clone());
let m = if let Some(reply_to) = &self.reply_to {
m.reply_to(reply_to.parse().expect("valid `smtp_reply_to`"))
} else {
m
};
let text =
SMTPSettings::render_template(&template.text, &variables).expect("valid `template`");
let m = if let Some(html) = &template.html {
m.multipart(lettre::message::MultiPart::alternative_plain_html(
text,
SMTPSettings::render_template(html, &variables).expect("valid `template`"),
))
} else {
m.body(text)
}
.expect("valid `message`");
use lettre::Transport;
let sender = self.create_transport();
match sender.send(&m) {
Ok(_) => Ok(()),
Err(error) => {
error!(
"Failed to send email to {}!\nlettre error: {}",
address, error
);
Err(Error::EmailFailed)
}
}
}
}