From 408b084ee6a259801a37dc8bd920e9e899fde18d Mon Sep 17 00:00:00 2001 From: Edouard Vanbelle Date: Tue, 2 Jun 2026 00:21:34 +0200 Subject: [PATCH] chore: SMTP is a simple implementation, recommand local server to have a spool --- docs/config/env.md | 20 +++++++++++++++++++ example.env | 6 ++++++ .../services/smtp_email_sender.rs | 13 ++++++++++++ 3 files changed, 39 insertions(+) diff --git a/docs/config/env.md b/docs/config/env.md index 745b63ad..ad7a7739 100644 --- a/docs/config/env.md +++ b/docs/config/env.md @@ -185,6 +185,26 @@ Used by the magic-link invitation flow and the login-via-email flow. When `OXICL | `OXICLOUD_SMTP_FROM` | — | `From:` mailbox; bare address or RFC 5322 name-address (`OxiCloud `) | | `OXICLOUD_SMTP_TLS` | `starttls` | Transport encryption: `starttls`, `tls`, or `none` (emits startup WARN) | +### Reliability and retries + +OxiCloud does **not** spool mail. Each `send()` is a single attempt: if the remote SMTP server is unreachable, slow, or temporarily refusing the message, the send fails and the error is logged — there is no in-process retry, queue, or dead-letter handling. This keeps the HTTP path fast and the binary small at the cost of durability guarantees during a relay outage. + +For production deployments where you cannot afford to drop invitation mail during a brief relay outage, **point OxiCloud at a local MTA configured as a smarthost** (Postfix, OpenSMTPD, exim, or `msmtp-mta`/`nullmailer` for minimal setups). The local MTA owns the durable queue: it accepts the message from OxiCloud in milliseconds over the loopback, then retries with its own exponential backoff against your real upstream relay until the message is delivered or the queue lifetime expires. + +Typical local-relay config: + +```env +OXICLOUD_SMTP_HOST=127.0.0.1 +OXICLOUD_SMTP_PORT=25 +OXICLOUD_SMTP_TLS=none # loopback only — never over the network +OXICLOUD_SMTP_FROM=OxiCloud +# OXICLOUD_SMTP_USER / _PASS unset — local MTA accepts loopback unauthenticated +``` + +Then configure the local MTA's smarthost / relayhost to your upstream provider (SendGrid, Amazon SES, your corporate relay, etc.). Verify durability by stopping the upstream relay, sending an invitation, restarting the relay, and confirming the mail eventually arrives. + +If you point `OXICLOUD_SMTP_HOST` directly at a remote SMTP server, treat the absence of retries as a documented constraint: a brief network glitch during invitation flow is a lost invite, and the recipient will need to be re-invited. + ## Magic-Link Authentication Configures the invite-by-email and login-via-email flows. Both require SMTP to be configured above. diff --git a/example.env b/example.env index 58d8fbe8..1344e3b0 100644 --- a/example.env +++ b/example.env @@ -318,6 +318,12 @@ OXICLOUD_WOPI_ENABLED=false # Used by the magic-link invitation flow (sharing with someone by email) and # the login-via-email flow. When HOST is empty (the default), the feature is # disabled and any endpoint that needs email returns 503. +# +# OxiCloud does NOT spool mail or retry failed sends — each delivery is a +# single attempt. For durability against brief upstream outages, point this +# at a local MTA (Postfix, OpenSMTPD, msmtp-mta, …) configured as a +# smarthost. The local MTA owns the queue and retries against your real +# relay. See docs/config/env.md → "Reliability and retries" for the recipe. # SMTP server hostname or IP. Empty = feature disabled. #OXICLOUD_SMTP_HOST=smtp.example.com diff --git a/src/infrastructure/services/smtp_email_sender.rs b/src/infrastructure/services/smtp_email_sender.rs index b2aef8de..bc25ed47 100644 --- a/src/infrastructure/services/smtp_email_sender.rs +++ b/src/infrastructure/services/smtp_email_sender.rs @@ -8,6 +8,19 @@ //! On startup the `From:` mailbox is parsed once and cached. Bad config //! (unparseable `from`, missing `host`) is reported during construction //! so the server fails fast rather than at first send. +//! +//! # No retry / no spool — by design +//! +//! `send()` makes a single attempt against the configured relay. If the +//! relay is unreachable, slow, or returns a transient error, the call +//! returns `Err` and the message is gone. There is no in-process queue, +//! no exponential backoff, no dead-letter handling. +//! +//! Operators who need durability across upstream relay outages should +//! point `OXICLOUD_SMTP_HOST` at a local MTA (Postfix, OpenSMTPD, +//! msmtp-mta, …) configured as a smarthost — the local MTA owns the +//! retry queue. See `docs/config/env.md` → "Reliability and retries" +//! for the recipe. use async_trait::async_trait; use lettre::message::{Mailbox, MultiPart, SinglePart, header::ContentType};