mod email_sender;
mod template;
use async_trait::async_trait;
pub use email_sender::EmailSender;
use include_dir::Dir;
use serde::{Deserialize, Serialize};
use sidekiq::Worker;
use self::template::Template;
use super::{app::AppContext, worker::AppWorker, Result};
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,
}
#[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,
}
#[derive(Default)]
#[allow(clippy::module_name_repetitions)]
pub struct MailerOpts {
from: String,
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
.map_err(Box::from)?;
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,
},
)
.await
}
}
#[allow(clippy::module_name_repetitions)]
pub struct MailerWorker {
pub ctx: AppContext,
}
impl AppWorker<Email> for MailerWorker {
fn build(ctx: &AppContext) -> Self {
Self { ctx: ctx.clone() }
}
}
#[async_trait]
impl Worker<Email> for MailerWorker {
fn opts() -> sidekiq::WorkerOpts<Email, Self> {
sidekiq::WorkerOpts::new().queue("mailer")
}
async fn perform(&self, email: Email) -> sidekiq::Result<()> {
if let Some(mailer) = &self.ctx.mailer {
Ok(mailer.mail(&email).await.map_err(Box::from)?)
} else {
Err(sidekiq::Error::Message(
"attempting to send email but no email sender configured".to_string(),
))
}
}
}