Web

Mail

The optional mail feature sends plain-text mail over SMTP with STARTTLS (rustls). Every mail goes to an outbox first and is delivered in the background, so a visitor never waits for the mail server and an outage never becomes an error page (ADR 0015).

Sending#

use rustclamp::mail::Message;

if let Some(owner) = request.mailer().admin() {
    let text = format!("From: {name} <{email}>\n\n{message}");
    request.mailer().send(request.db(), Message::new(owner, "Contact form", &text))?;
}

send only adds the mail to the mail_outbox table. A thread started by web::App delivers it at startup and then every minute, oldest first.

Three limits on every send#

Per routeThrottle every POST that sends mail: Throttle::from_config(config, "CONTACT_PER_MINUTE", 3).
Per dayAt most MAIL_DAILY_CAP (default 100) mails per UTC day, counted in the database. The rest wait in the outbox for the next day: never dropped, never sent anyway. The first time the cap is hit each day is logged as a warning.
RecipientsA Recipient comes from config (Recipient::from_config, mailer.admin() for MAIL_ADMIN_ADDRESS) or a logged-in user (Recipient::user). There is no way to make one from a string, so an address typed into a form can never receive mail.

Count sends, not submissions: a form that notifies you and confirms to the sender is two mails against the cap. A failed delivery is retried after 1, 2, 4 … minutes (at most an hour).

Settings#

MAIL_MAILER=smtp            # log (default) writes a line per mail and sends nothing
MAIL_HOST=smtp-relay.brevo.com
MAIL_PORT=587
MAIL_USERNAME=...
MAIL_PASSWORD=...           # only ever sent over TLS
MAIL_ENCRYPTION=tls         # STARTTLS is required; none only for a local Mailpit
[email protected]
MAIL_FROM_NAME="Example"
[email protected]
MAIL_DAILY_CAP=100

The app reads them in app/config/mail.rs and passes it to web::App as mail: config::mail. If the server does not offer STARTTLS, delivery fails rather than falling back to plaintext. Subjects and names in any language are encoded for mail, and bodies are UTF-8.

On a server that is already set up, add the MAIL_* keys to its .env by hand: deploy playbooks usually write .env only once.