chore: SMTP is a simple implementation, recommand local server to have a spool

This commit is contained in:
Edouard Vanbelle
2026-06-02 00:21:34 +02:00
parent 8a05ee00cb
commit 408b084ee6
3 changed files with 39 additions and 0 deletions
+20
View File
@@ -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 <noreply@example.com>`) |
| `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 <noreply@example.com>
# 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.
+6
View File
@@ -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
@@ -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};