# Usage Guide
This guide covers the current public Mailbridge API for sending transactional
email from Rust services.
## Install
```toml
[dependencies]
mailbridge = "0.1"
```
Default features enable:
- Hyvor Relay HTTP sending;
- Rustls TLS;
- in-memory queueing;
- local rate limiting.
For SMTP:
```toml
[dependencies]
mailbridge = { version = "0.1", features = ["smtp"] }
```
For durable queues:
```toml
[dependencies]
mailbridge = { version = "0.1", features = ["queue-sqlite"] }
```
Use only the queue backend features your application needs.
## Environment Configuration
`MailbridgeConfig::from_env()` reads `RELAY_*` environment variables.
Required for Hyvor Relay HTTP:
```sh
RELAY_API_BASE_URL=https://relay.example.com/api/console
RELAY_API_KEY=your-api-key
RELAY_ALLOWED_FROM_DOMAINS=example.com,example.org
```
Optional default sender:
```sh
RELAY_DEFAULT_FROM_NAME=Example App
RELAY_DEFAULT_FROM_EMAIL=no-reply@example.com
```
Optional rate and retry settings:
```sh
RELAY_GLOBAL_RATE_PER_SECOND=50
RELAY_DOMAIN_RATE_PER_SECOND=10
RELAY_MAX_RETRIES=5
RELAY_RETRY_BASE_DELAY_MS=500
RELAY_REQUEST_TIMEOUT_SECS=15
```
Optional SMTP settings:
```sh
RELAY_SMTP_HOST=smtp.example.com
RELAY_SMTP_PORT=587
RELAY_SMTP_USERNAME=relay-user
RELAY_SMTP_PASSWORD=relay-password
```
Optional queue backend:
```sh
RELAY_QUEUE_BACKEND=memory
```
SQLite:
```sh
RELAY_QUEUE_BACKEND=sqlite
RELAY_QUEUE_SQLITE_PATH=./mailbridge-queue.sqlite
```
PostgreSQL:
```sh
RELAY_QUEUE_BACKEND=postgres
RELAY_QUEUE_POSTGRES_URL=postgres://user:password@localhost/mailbridge
```
ScyllaDB:
```sh
RELAY_QUEUE_BACKEND=scylla
RELAY_QUEUE_SCYLLA_URI=127.0.0.1:9042
RELAY_QUEUE_SCYLLA_KEYSPACE=mailbridge
RELAY_QUEUE_SCYLLA_TABLE=mail_queue
```
Keep secrets in `.env` or your deployment secret manager. Do not commit
credentials.
## Send With Hyvor Relay
```rust
use mailbridge::{EmailMessage, HyvorRelayProvider, MailClient, MailbridgeConfig};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let config = MailbridgeConfig::from_env()?;
let provider = HyvorRelayProvider::from_config(&config)?;
let client = MailClient::try_from_config(provider, &config).await?;
let message = EmailMessage::builder()
.from("Example App", "no-reply@example.com")?
.to("User", "user@example.net")?
.subject("Welcome")
.text("Thanks for signing up.")
.build()?;
let receipt = client.send(message).await?;
println!("{}", receipt.message_id());
Ok(())
}
```
Runnable example:
```sh
cargo run --example send_http
```
## Send HTML
```rust
let message = EmailMessage::builder()
.from("Example App", "no-reply@example.com")?
.to("User", "user@example.net")?
.subject("Receipt")
.text("Your receipt is attached.")
.html("<p>Your receipt is attached.</p>")
.build()?;
```
## Add CC, BCC, Headers, And Attachments
```rust
let message = EmailMessage::builder()
.from("Example App", "no-reply@example.com")?
.to("User", "user@example.net")?
.cc("Support", "support@example.com")?
.bcc("Audit", "audit@example.com")?
.subject("Invoice")
.text("Invoice attached.")
.header("X-Request-Id", "req_123")?
.attachment("invoice.txt", "text/plain", b"invoice contents".to_vec())?
.build()?;
```
Mailbridge validates email addresses, message bodies, headers, attachments,
and allowed sender domains before making provider requests.
## Queue A Message
```rust
use mailbridge::{
EmailMessage, HyvorRelayProvider, MailClient, MailbridgeConfig, QueueWorker,
QueueWorkerConfig,
};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let config = MailbridgeConfig::from_env()?;
let provider = HyvorRelayProvider::from_config(&config)?;
let client = MailClient::try_from_config(provider, &config).await?;
let queue = client
.queue()
.cloned()
.unwrap_or_else(mailbridge::QueueHandle::memory_default);
let worker = QueueWorker::new(
client.clone().with_queue(queue.clone()),
queue,
QueueWorkerConfig::new("worker-1"),
);
let message = EmailMessage::builder()
.from("Example App", "no-reply@example.com")?
.to("User", "user@example.net")?
.subject("Queued message")
.text("This was queued first.")
.build()?;
client.enqueue(message).await?;
worker.run_once().await?;
Ok(())
}
```
Runnable example:
```sh
cargo run --example queue_worker
```
## Send With SMTP
Enable the `smtp` feature and provide SMTP configuration:
```toml
[dependencies]
mailbridge = { version = "0.1", features = ["smtp"] }
```
```sh
RELAY_SMTP_HOST=smtp.example.com
RELAY_SMTP_PORT=587
RELAY_SMTP_USERNAME=relay-user
RELAY_SMTP_PASSWORD=relay-password
```
Then build `SmtpClient` from the same config:
```rust
use mailbridge::{EmailMessage, MailClient, MailbridgeConfig, SmtpClient};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let config = MailbridgeConfig::from_env()?;
let provider = SmtpClient::from_config(&config)?;
let client = MailClient::try_from_config(provider, &config).await?;
let message = EmailMessage::builder()
.from("Example App", "no-reply@example.com")?
.to("User", "user@example.net")?
.subject("SMTP test")
.text("Sent through SMTP.")
.build()?;
client.send(message).await?;
Ok(())
}
```
Current SMTP support is generic username/password SMTP over STARTTLS. Gmail,
Microsoft 365, Yahoo, Yandex, OAuth/XOAUTH2, and provider-specific presets are
planned in the roadmap.
## Feature Flags
Common features:
```text
api HTTP provider support through reqwest
hyvor-relay Hyvor Relay provider
smtp SMTP provider through lettre
rustls Rustls TLS backend
native-tls Native TLS backend
queue-memory in-memory queue
queue-sqlite SQLite queue backend
queue-postgres PostgreSQL queue backend
queue-scylla ScyllaDB queue backend
rate-limit local rate limiting
telemetry tracing events
dotenv load .env before reading RELAY_* variables
```
`rustls` and `native-tls` are mutually exclusive.
Mailbridge uses `reqwest` 0.13 for HTTP providers. The `rustls` feature maps to
`reqwest/rustls`, while `native-tls` maps to `reqwest/native-tls`. Default
features use Rustls so applications get a pure-Rust TLS stack for Relay HTTP
calls unless they explicitly opt into `native-tls`.
Queue backends using SQLx 0.9 enable `sqlx/runtime-tokio` and `sqlx/tls-rustls`
through the Mailbridge queue feature flags. Because SQLx 0.9 requires Rust
1.94, Mailbridge's crate-level MSRV is Rust 1.94 or newer.
## Error Handling
Most APIs return `mailbridge::Result<T>`, using `MailError` for validation,
configuration, provider rejection, and temporary transport failures.
Queue workers use retry classification to decide whether a failed message can
be retried or should be dead-lettered.
## Safety Notes
- Keep `.env` out of Git.
- Use allowed sender domains to prevent accidental spoofing.
- Do not log API keys, SMTP passwords, OAuth tokens, message bodies, attachment
content, or full recipient lists.
- Prefer dedicated transactional relays for production application email.
Personal mailbox SMTP is planned, but it is not a replacement for a relay at
transactional volume.