151 lines
4.1 KiB
Rust
151 lines
4.1 KiB
Rust
use lettre::message::header::ContentType;
|
|
use lettre::transport::smtp::authentication::Credentials;
|
|
use lettre::transport::smtp::response::Response;
|
|
use lettre::{AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor};
|
|
use mrml::mjml::Mjml;
|
|
use mrml::prelude::parse::Error as MjmlParserError;
|
|
use mrml::prelude::render::{Error as MjmlRenderError, Options};
|
|
use serde::{Deserialize, Serialize};
|
|
use tera::{Context, Tera};
|
|
|
|
// error
|
|
#[derive(thiserror::Error)]
|
|
// the rest
|
|
#[derive(Debug)]
|
|
pub enum Error {
|
|
#[error("Template error: {0}")]
|
|
Template(#[from] tera::Error),
|
|
|
|
#[error("Mjml parser error: {0}")]
|
|
MjmlParser(#[from] MjmlParserError),
|
|
|
|
#[error("Mjml render error: {0}")]
|
|
MjmlRender(#[from] MjmlRenderError),
|
|
|
|
#[error("Invalid email address: {0}")]
|
|
EmailAddress(#[from] lettre::address::AddressError),
|
|
|
|
#[error("Invalid email message: {0}")]
|
|
EmailMessage(#[from] lettre::error::Error),
|
|
|
|
#[error("Error when sending email: {0}")]
|
|
EmailSend(#[from] lettre::transport::smtp::Error),
|
|
|
|
#[error("Invalid {0}")]
|
|
InvalidConfig(&'static str),
|
|
}
|
|
|
|
const TEMPLATE_EXT: &str = ".mjml.tera";
|
|
|
|
pub fn render_template<C: Serialize>(
|
|
name: &str,
|
|
template_dir: &str,
|
|
content: C,
|
|
) -> Result<String, Error> {
|
|
let templates = format!("{template_dir}/**/*{TEMPLATE_EXT}");
|
|
|
|
// Initialize tera templates
|
|
let tera = Tera::new(&templates)?;
|
|
|
|
// Construct context
|
|
let context = Context::from_serialize(content)?;
|
|
|
|
// Render template
|
|
let template_name = format!("{name}{TEMPLATE_EXT}");
|
|
Ok(tera.render(&template_name, &context)?)
|
|
}
|
|
|
|
pub fn render_email(mjml: &str) -> Result<String, Error> {
|
|
// Parse mjml
|
|
let root = Mjml::parse(mjml)?;
|
|
|
|
// Render to html
|
|
let opts = Options::default();
|
|
Ok(root.render(&opts)?)
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum Transport {
|
|
Unencrypted,
|
|
Tls,
|
|
StartTls,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct Config {
|
|
pub template_dir: String,
|
|
pub from: String,
|
|
pub transport: Transport,
|
|
pub host: String,
|
|
pub port: Option<u16>,
|
|
pub username: Option<String>,
|
|
pub password: Option<String>,
|
|
}
|
|
|
|
impl Config {
|
|
pub fn status(&self) -> Result<String, &'static str> {
|
|
match self.transport {
|
|
Transport::Unencrypted => match self.port {
|
|
Some(port) => Ok(format!("Using \"{}:{port}\" to send emails", self.host)),
|
|
None => Err("Port not provided."),
|
|
},
|
|
Transport::Tls | Transport::StartTls => {
|
|
if self.username.is_none() {
|
|
return Err("Username not provided.");
|
|
}
|
|
|
|
if self.password.is_none() {
|
|
return Err("Password not provided.");
|
|
}
|
|
|
|
Ok(format!("Using \"{}\" to send emails", self.host))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn create_credentials(config: &Config) -> Result<Credentials, Error> {
|
|
let username = config
|
|
.username
|
|
.as_deref()
|
|
.ok_or(Error::InvalidConfig("username"))?;
|
|
let password = config
|
|
.password
|
|
.as_deref()
|
|
.ok_or(Error::InvalidConfig("password"))?;
|
|
|
|
Ok(Credentials::from((username, password)))
|
|
}
|
|
|
|
pub async fn send_email(
|
|
config: &Config,
|
|
user: &str,
|
|
email_title: &str,
|
|
html: String,
|
|
) -> Result<Response, Error> {
|
|
let m = Message::builder()
|
|
.from(config.from.parse()?)
|
|
.to(user.parse()?)
|
|
.subject(email_title)
|
|
.header(ContentType::TEXT_HTML)
|
|
.body(html)?;
|
|
|
|
type SmtpTransport = AsyncSmtpTransport<Tokio1Executor>;
|
|
|
|
let mailer = match config.transport {
|
|
Transport::Unencrypted => {
|
|
let port = config.port.ok_or(Error::InvalidConfig("port"))?;
|
|
SmtpTransport::builder_dangerous(&config.host).port(port)
|
|
}
|
|
Transport::Tls => {
|
|
SmtpTransport::relay(&config.host)?.credentials(create_credentials(config)?)
|
|
}
|
|
Transport::StartTls => {
|
|
SmtpTransport::starttls_relay(&config.host)?.credentials(create_credentials(config)?)
|
|
}
|
|
}
|
|
.build();
|
|
|
|
Ok(mailer.send(m).await?)
|
|
}
|