# SMTP Configuration – Concept **Status:** Implemented **Last updated:** 2026-03-12 --- ## 1. Goal Enable configurable SMTP for sending transactional emails (join confirmation, user confirmation, password reset). Configuration via **environment variables** and **Admin Settings** (database), with the same precedence pattern as OIDC and Vereinfacht: **ENV overrides Settings**. Include a **test email** action in Settings (button + recipient field) with clear success/error feedback. --- ## 2. Scope - **In scope:** SMTP server configuration (host, port, credentials, TLS/SSL), sender identity (from-name, from-email), test email from Settings UI, warning when SMTP is not configured in production, specific error messages per failure category, graceful delivery errors in AshAuthentication senders. - **Out of scope:** Separate adapters per email type; retry queues. --- ## 3. Configuration Sources | Source | Priority | Use case | |----------|----------|-----------------------------------| | ENV | 1 | Production, Docker, 12-factor | | Settings | 2 | Admin UI, dev without ENV | When an ENV variable is set, the corresponding Settings field is read-only in the UI (with hint "Set by environment"). --- ## 4. SMTP Parameters | Parameter | ENV | Settings attribute | Notes | |----------------|------------------------|---------------------|---------------------------------------------| | Host | `SMTP_HOST` | `smtp_host` | e.g. `smtp.example.com` | | Port | `SMTP_PORT` | `smtp_port` | Default 587 (TLS), 465 (SSL), 25 (plain) | | Username | `SMTP_USERNAME` | `smtp_username` | Optional if no auth | | Password | `SMTP_PASSWORD` | `smtp_password` | Sensitive, not shown when set | | Password file | `SMTP_PASSWORD_FILE` | — | Docker/Secrets: path to file with password | | TLS/SSL | `SMTP_SSL` | `smtp_ssl` | `tls` / `ssl` / `none` (default: tls) | | Sender name | `MAIL_FROM_NAME` | `smtp_from_name` | Display name in "From" header (default: Mila)| | Sender email | `MAIL_FROM_EMAIL` | `smtp_from_email` | Address in "From" header; must match SMTP user on most servers | **Important:** On most SMTP servers (e.g. Postfix with strict relay policies) the sender email (`smtp_from_email`) must be the same address as `smtp_username` or an alias that is owned by that account. --- ## 5. Password from File Support **SMTP_PASSWORD_FILE** (path to file containing the password), same pattern as `OIDC_CLIENT_SECRET_FILE` in `runtime.exs`. Read once at runtime; `SMTP_PASSWORD` ENV overrides file if both are set. --- ## 6. Behaviour When SMTP Is Not Configured - **Dev/Test:** Keep current adapters (`Swoosh.Adapters.Local`, `Swoosh.Adapters.Test`). No change. - **Production:** If neither ENV nor Settings provide SMTP (no host): - Show a warning in the Settings UI. - Delivery attempts silently fall back to the Local adapter (no crash). --- ## 7. Test Email (Settings UI) - **Location:** SMTP / E-Mail section in Global Settings. - **Elements:** Input for recipient, submit button inside a `phx-submit` form. - **Behaviour:** Sends one email using current SMTP config and `mail_from/0`. Returns `{:ok, _}` or `{:error, classified_reason}`. - **Error categories:** `:sender_rejected`, `:auth_failed`, `:recipient_rejected`, `:tls_failed`, `:connection_failed`, `{:smtp_error, message}` — each shows a specific human-readable message in the UI. - **Permission:** Reuses existing Settings page authorization (admin). --- ## 8. Sender Identity (`mail_from`) `Mv.Mailer.mail_from/0` returns `{name, email}`. Priority: 1. `MAIL_FROM_NAME` / `MAIL_FROM_EMAIL` ENV variables 2. `smtp_from_name` / `smtp_from_email` in Settings (DB) 3. Hardcoded defaults: `{"Mila", "noreply@example.com"}` Provided by `Mv.Config.mail_from_name/0` and `Mv.Config.mail_from_email/0`. --- ## 9. AshAuthentication Senders Both `SendPasswordResetEmail` and `SendNewUserConfirmationEmail` use `Mv.Mailer.deliver/1` (not `deliver!/1`). Delivery failures are logged (`Logger.error`) and not re-raised, so they never crash the caller process. AshAuthentication ignores the return value of `send/3`. --- ## 10. TLS / SSL in OTP 27 OTP 26+ enforces `verify_peer` by default, which fails for self-signed or internal SMTP server certificates. By default, TLS certificate verification is relaxed (`verify_none`) so self-signed or internal SMTP servers work. For public SMTP providers (Gmail, Mailgun, etc.) you can enable verification: - **ENV (prod):** Set `SMTP_VERIFY_PEER=true` (or `1`/`yes`) when configuring SMTP via environment variables in `config/runtime.exs`. This sets `config :mv, :smtp_verify_peer` and is used for both boot-time and per-send config. - **Default:** `false` (verify_none) for backward compatibility and internal/self-signed certs. Both `tls_options` (STARTTLS, port 587) and `sockopts` (direct SSL, port 465) use the same verify mode. The logic is duplicated in `config/runtime.exs` (boot) and `Mv.Mailer.smtp_config/0` (Settings-only); keep in sync. --- ## 11. Summary Checklist - [x] ENV: `SMTP_HOST`, `SMTP_PORT`, `SMTP_USERNAME`, `SMTP_PASSWORD`, `SMTP_PASSWORD_FILE`, `SMTP_SSL`. - [x] ENV: `MAIL_FROM_NAME`, `MAIL_FROM_EMAIL` for sender identity. - [x] Settings: attributes and UI for host, port, username, password, TLS/SSL, from-name, from-email. - [x] Password from file: `SMTP_PASSWORD_FILE` supported in `runtime.exs`. - [x] Mailer: Swoosh SMTP adapter configured from merged ENV + Settings when SMTP is configured. - [x] Per-request SMTP config via `Mv.Mailer.smtp_config/0` for Settings-only scenarios. - [x] TLS certificate validation relaxed for OTP 27 (tls_options + sockopts). - [x] Prod warning: clear message in Settings when SMTP is not configured. - [x] Test email: form with recipient field, translatable content, classified success/error messages. - [x] AshAuthentication senders: graceful error handling (no crash on delivery failure). - [x] Gettext for all new UI strings, translated to German. - [x] Docs and code guidelines updated. --- ## 12. Follow-up / Future Work - **SMTP password at-rest encryption:** The `smtp_password` attribute is currently stored in plaintext in the `settings` table. It is excluded from default reads (same pattern as `oidc_client_secret`); both are read only via explicit select when needed. For production systems at-rest encryption (e.g. with [Cloak](https://hexdocs.pm/cloak)) should be considered and tracked as a follow-up issue. - **Error classification:** SMTP error categorization currently uses substring matching on server messages (e.g. "535", "authentication"). A more robust approach would be to pattern-match on `gen_smtp` error tuples first where possible, and fall back to string analysis only when needed. Server wording varies; consider extending patterns as new providers are used.