pub mod noop_service;
#[cfg(feature = "resend")]
pub mod resend;
pub mod test_service;
use async_trait::async_trait;
use error_stack::{Report, ResultExt};
use thiserror::Error;
use super::{templates::EmailTemplate, Email};
#[derive(Debug, Error)]
pub enum EmailError {
#[error("Template render error")]
Rendering,
#[error("Generic failure")]
Failed,
#[error("Email was too large")]
TooLarge,
}
#[async_trait]
pub trait EmailService: Send + Sync {
async fn send(&self, email: Email) -> Result<(), EmailError>;
}
pub struct EmailSender {
default_from: String,
templates: tera::Tera,
service: Box<dyn EmailService>,
}
impl EmailSender {
pub fn new(
default_from: String,
templates: tera::Tera,
service: Box<dyn EmailService>,
) -> Self {
Self {
default_from,
templates,
service,
}
}
pub async fn send(&self, mut email: Email) -> Result<(), Report<EmailError>> {
if email.from.is_empty() {
email.from = self.default_from.clone();
}
if !email.html.is_empty() {
let inliner = css_inline::CSSInliner::options()
.load_remote_stylesheets(false)
.build();
email.html = inliner
.inline(&email.html)
.change_context(EmailError::Rendering)?;
}
self.service.send(email).await?;
Ok(())
}
pub async fn send_template(
&self,
to: String,
template: impl EmailTemplate,
) -> Result<(), Report<EmailError>> {
let email = template
.into_email(&self.templates, to)?
.from(self.default_from.clone())
.build();
self.send(email).await?;
Ok(())
}
}
#[cfg(feature = "email_provider")]
pub fn email_service_from_name(name: &str, api_key: String) -> Box<dyn EmailService> {
match name {
"none" => Box::new(noop_service::NoopEmailService {}),
#[cfg(feature = "resend")]
"resend" => Box::new(resend::ResendEmailService::new(api_key)),
_ => panic!("Unknown email service: {}", name),
}
}