# 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.
[](https://crates.io/crates/rok-mail) [](https://docs.rs/rok-mail) [](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
| `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