rok-mail 0.6.0

Email support for the rok ecosystem — Mailable trait, log/SMTP drivers
Documentation

rok-mail

Typed email framework for Axum — SMTP, Resend, and Postmark drivers with HTML + plain text templates.

Part of the Rok Framework — a full-stack Rust web framework built on Axum 0.8 and SQLx 0.8.

crates.io docs.rs MIT

Features

  • Mailable trait for defining emails as plain Rust structs with builder-style Mail construction
  • Mail::send(mailable, recipient).await? one-liner dispatch
  • SMTP driver via lettre with TLS support and connection pooling
  • Resend API driver for transactional email at scale
  • Postmark API driver for high-deliverability transactional email
  • Log driver for local development — prints email content to stdout instead of sending
  • MailLayer Tower middleware for injecting the mailer into Axum handlers via State
  • Queue integration: mailable.queue().await? dispatches sending as a background job via rok-queue

Installation

[dependencies]
rok-mail = { version = "0.2", features = ["smtp", "axum"] }

Quick Start

use rok_mail::{Mail, Mailable, MailDriver};
use axum::{extract::State, response::IntoResponse, Json};
use std::sync::Arc;

pub struct WelcomeEmail {
    pub name:  String,
    pub email: String,
}

impl Mailable for WelcomeEmail {
    fn build(&self) -> Mail {
        Mail::new()
            .to(&self.email)
            .subject(format!("Welcome to Rok, {}!", self.name))
            .html(format!(
                "<h1>Hi {}!</h1><p>Your account is ready. <a href=\"https://app.rok.rs\">Get started</a>.</p>",
                self.name
            ))
            .text(format!("Hi {}! Your account is ready. Visit https://app.rok.rs", self.name))
    }
}

async fn register(
    State(mailer): State<Arc<dyn MailDriver>>,
    Json(body): Json<RegisterRequest>,
) -> impl IntoResponse {
    let user = User::create(&body).await?;

    WelcomeEmail { name: user.name.clone(), email: user.email.clone() }
        .send(&*mailer)
        .await?;

    Json(user)
}

Core API

Mail builder

let mail = Mail::new()
    .to("alice@example.com")
    .cc("manager@example.com")
    .bcc("archive@example.com")
    .reply_to("support@example.com")
    .from("noreply@example.com")
    .subject("Your invoice is ready")
    .html("<p>Please find your invoice attached.</p>")
    .text("Please find your invoice attached.")
    .attach_path("invoice.pdf", "./storage/invoices/001.pdf")
    .header("X-Priority", "1");

Driver configuration

use rok_mail::MailConfig;

// SMTP
let mailer = MailConfig::smtp("smtps://user:password@smtp.example.com:465")
    .from("noreply@example.com")
    .from_name("My App")
    .build()?;

// Resend
let mailer = MailConfig::resend(std::env::var("RESEND_API_KEY")?)
    .from("noreply@example.com")
    .build()?;

// Postmark
let mailer = MailConfig::postmark(std::env::var("POSTMARK_TOKEN")?)
    .from("noreply@example.com")
    .build()?;

// Log (development)
let mailer = MailConfig::log().build();

Queue integration

// Send in the background via rok-queue instead of blocking the request
WelcomeEmail { name: user.name.clone(), email: user.email.clone() }
    .queue()
    .await?;
// returns immediately — email is sent by a queue worker

// Delayed send
WelcomeEmail { .. }
    .queue_in(std::time::Duration::from_secs(300))
    .await?;

Feature Flags

Flag Description Default
smtp SMTP driver via lettre No
resend Resend API driver No
postmark Postmark API driver No
axum MailLayer middleware and State<Arc<dyn MailDriver>> support No
queue Mailable::queue() integration with rok-queue No

Integration

rok-mail is used by rok-auth for email verification and password reset flows, and by rok-notification for the email channel. Configure the mailer once and inject it via MailLayer:

let mailer = MailConfig::smtp(&config.smtp_url).from(&config.mail_from).build()?;

let app = Router::new()
    .route("/register", axum::routing::post(register))
    .layer(rok_mail::MailLayer::new(Arc::new(mailer)));

License

MIT