mod email_sender;
mod template;
use async_trait::async_trait;
pub use email_sender::EmailSender;
use include_dir::Dir;
use serde::{Deserialize, Serialize};
use tracing::error;
use self::template::Template;
use super::{app::AppContext, Result};
use crate::prelude::BackgroundWorker;
pub const DEFAULT_FROM_SENDER: &str = "System <system@example.com>";
#[derive(Debug, Clone, Default)]
pub struct Args {
pub from: Option<String>,
pub to: String,
pub reply_to: Option<String>,
pub locals: serde_json::Value,
pub bcc: Option<String>,
pub cc: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct Email {
pub from: Option<String>,
pub to: String,
pub reply_to: Option<String>,
pub subject: String,
pub text: String,
pub html: String,
pub bcc: Option<String>,
pub cc: Option<String>,
}
#[derive(Default, Debug)]
#[allow(clippy::module_name_repetitions)]
pub struct MailerOpts {
pub from: String,
pub reply_to: Option<String>,
}
#[async_trait]
pub trait Mailer {
#[must_use]
fn opts() -> MailerOpts {
MailerOpts {
from: DEFAULT_FROM_SENDER.to_string(),
..Default::default()
}
}
async fn mail(ctx: &AppContext, email: &Email) -> Result<()> {
let opts = Self::opts();
let mut email = email.clone();
email.from = Some(email.from.unwrap_or_else(|| opts.from.clone()));
email.reply_to = email.reply_to.or_else(|| opts.reply_to.clone());
MailerWorker::perform_later(ctx, email.clone()).await?;
Ok(())
}
async fn mail_template(ctx: &AppContext, dir: &Dir<'_>, args: Args) -> Result<()> {
let content = Template::new(dir).render(&args.locals)?;
Self::mail(
ctx,
&Email {
from: args.from.clone(),
to: args.to.clone(),
reply_to: args.reply_to.clone(),
subject: content.subject,
text: content.text,
html: content.html,
bcc: args.bcc.clone(),
cc: args.cc.clone(),
},
)
.await
}
}
#[allow(clippy::module_name_repetitions)]
pub struct MailerWorker {
pub ctx: AppContext,
}
#[async_trait]
impl BackgroundWorker<Email> for MailerWorker {
fn queue() -> Option<String> {
Some("mailer".to_string())
}
fn build(ctx: &AppContext) -> Self {
Self { ctx: ctx.clone() }
}
async fn perform(&self, email: Email) -> crate::Result<()> {
if let Some(mailer) = &self.ctx.mailer {
let res = mailer.mail(&email).await;
match res {
Ok(res) => Ok(res),
Err(err) => {
error!(err = err.to_string(), "mailer error");
Err(err)
}
}
} else {
let err = crate::Error::Message(
"attempting to send email but no email sender configured".to_string(),
);
error!(err = err.to_string(), "mailer error");
Err(err)
}
}
}