torii-mailer 0.5.2

Pluggable email system for the torii authentication ecosystem
Documentation
use crate::{
    Email, MailerError,
    templates::{TemplateContext, TemplateData, TemplateEngine},
};

pub struct MagicLinkEmail;

impl MagicLinkEmail {
    pub async fn build<T: TemplateEngine>(
        engine: &T,
        from: &str,
        to: &str,
        magic_link: &str,
        context: TemplateContext,
    ) -> Result<Email, MailerError> {
        let template_data = TemplateData::new()
            .insert("context", &context)?
            .insert("magic_link", magic_link)?;

        let html_body = engine
            .render_html("magic_link", template_data.clone())
            .await?;
        let text_body = engine.render_text("magic_link", template_data).await?;

        Email::builder()
            .from(from)
            .to(to)
            .subject(format!("Sign in to {}", context.app_name))
            .html_body(html_body)
            .text_body(text_body)
            .build()
    }
}

pub struct WelcomeEmail;

impl WelcomeEmail {
    pub async fn build<T: TemplateEngine>(
        engine: &T,
        from: &str,
        to: &str,
        context: TemplateContext,
    ) -> Result<Email, MailerError> {
        let template_data = TemplateData::new().insert("context", &context)?;

        let html_body = engine.render_html("welcome", template_data.clone()).await?;
        let text_body = engine.render_text("welcome", template_data).await?;

        Email::builder()
            .from(from)
            .to(to)
            .subject(format!("Welcome to {}!", context.app_name))
            .html_body(html_body)
            .text_body(text_body)
            .build()
    }
}

pub struct PasswordResetEmail;

impl PasswordResetEmail {
    pub async fn build<T: TemplateEngine>(
        engine: &T,
        from: &str,
        to: &str,
        reset_link: &str,
        context: TemplateContext,
    ) -> Result<Email, MailerError> {
        let template_data = TemplateData::new()
            .insert("context", &context)?
            .insert("reset_link", reset_link)?;

        let html_body = engine
            .render_html("password_reset", template_data.clone())
            .await?;
        let text_body = engine.render_text("password_reset", template_data).await?;

        Email::builder()
            .from(from)
            .to(to)
            .subject(format!("Reset your {} password", context.app_name))
            .html_body(html_body)
            .text_body(text_body)
            .build()
    }
}

pub struct PasswordChangedEmail;

impl PasswordChangedEmail {
    pub async fn build<T: TemplateEngine>(
        engine: &T,
        from: &str,
        to: &str,
        context: TemplateContext,
    ) -> Result<Email, MailerError> {
        let template_data = TemplateData::new().insert("context", &context)?;

        let html_body = engine
            .render_html("password_changed", template_data.clone())
            .await?;
        let text_body = engine
            .render_text("password_changed", template_data)
            .await?;

        Email::builder()
            .from(from)
            .to(to)
            .subject(format!(
                "Your {} password has been changed",
                context.app_name
            ))
            .html_body(html_body)
            .text_body(text_body)
            .build()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::templates::AskamaTemplateEngine;

    #[tokio::test]
    async fn test_magic_link_email() {
        let engine = AskamaTemplateEngine::new();
        let context = TemplateContext {
            app_name: "Test App".to_string(),
            app_url: "https://testapp.com".to_string(),
            user_name: Some("John Doe".to_string()),
            user_email: Some("john@example.com".to_string()),
        };

        let email = MagicLinkEmail::build(
            &engine,
            "noreply@testapp.com",
            "john@example.com",
            "https://testapp.com/magic/abc123",
            context,
        )
        .await;

        assert!(email.is_ok());
        let email = email.unwrap();
        assert_eq!(email.to, vec!["john@example.com"]);
        assert_eq!(email.from, "noreply@testapp.com");
        assert!(email.subject.contains("Test App"));
        assert!(email.html_body.is_some());
        assert!(email.text_body.is_some());
    }

    #[tokio::test]
    async fn test_welcome_email() {
        let engine = AskamaTemplateEngine::new();
        let context = TemplateContext {
            app_name: "Test App".to_string(),
            app_url: "https://testapp.com".to_string(),
            user_name: Some("Jane Doe".to_string()),
            user_email: Some("jane@example.com".to_string()),
        };

        let email =
            WelcomeEmail::build(&engine, "noreply@testapp.com", "jane@example.com", context).await;

        assert!(email.is_ok());
        let email = email.unwrap();
        assert!(email.subject.contains("Welcome"));
    }
}