docs: README — app-email relay (mailrelay) feature section

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-29 08:59:19 +02:00
parent 3776d972f7
commit 9c0166dddd
+26
View File
@@ -2101,6 +2101,32 @@ Response format: `{"ok": true/false, "data": ..., "error": "...", "message": "..
---
## App-email relay (`internal/mailrelay/`)
Gives deployed apps outbound email (password resets, invites, confirmations) through one managed path —
**app → in-process SMTP shim → hub → Resend** — with the Resend key staying **hub-side** (never on the box).
Architecture **Shape 1**: the shim runs in-process inside the controller, reusing the existing hub client.
Implements `felhom.eu/documentation/audits/SPIKE-smtp-app-relay-2026-06-28.md`.
- **The shim (`internal/mailrelay/`)** is a `go-smtp` server with two listeners — `:2525` plaintext+STARTTLS
and `:2465` implicit-TLS (self-signed cert at boot). It advertises AUTH PLAIN+LOGIN and **accepts any
credentials, ignoring them** (apps send none; some require the offer). `Data` reads the raw message, enforces
the **From-header** domain allowlist (reject `5xx` **before** any hub call), then forwards the **raw MIME** to
the hub `POST /api/v1/mail` with the controller's hub Bearer key — **single-shot** (no retry, no spool in v1).
The hub HTTP status maps to an SMTP reply (2xx→250, 4xx→451, 5xx→554). Listeners bind to the app Docker
network only (the controller container joins `traefik-public`); apps reach the shim by `felhom-controller`.
- **Lifecycle** (`lifecycle.go`): the shim starts/stops at runtime to match the global app-email toggle (no
controller restart). Wired in `main.go`, gated on a configured hub + the `mail_relay` kill-switch.
- **Toggles + injection:** a **global** toggle (`settings.AppEmail`, Settings page) and a **per-app** toggle
(`AppConfig.EmailEnabled`, on the app's config page, shown only for apps with an `smtp_mapping`). When both are
on and the app declares `.felhom.yml` **`smtp_mapping`**, `stackEnv` injects the relay env at compose time
(host=shim, port=2525, security/from per the mapping, From=`<app>@felhom.eu`) — derived each compose, never
persisted to `app.yaml`. Config knobs: `mail_relay` (listeners, `shim_host`, `from_domains`, kill-switch).
- **What the box never holds:** the Resend key, or any durable mail queue. v2 (deferred) = a separate
`felhom-smtp-shim` container + accept-and-spool retry + a `Resend-Idempotency-Key`.
---
## Build & Deploy
### Build