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 route | Throttle every POST that sends mail: Throttle::from_config(config, "CONTACT_PER_MINUTE", 3). |
| Per day | At 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. |
| Recipients | A 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.