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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user