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](https://rok.rs) — a full-stack Rust web framework built on Axum 0.8 and SQLx 0.8.

[![crates.io](https://img.shields.io/crates/v/rok-mail)](https://crates.io/crates/rok-mail) [![docs.rs](https://img.shields.io/docsrs/rok-mail)](https://docs.rs/rok-mail) [![MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)

## 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

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

## Quick Start

```rust
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

```rust
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

```rust
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

```rust
// 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`:

```rust
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