feat: add smtp settings
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Simon 2026-03-12 13:39:48 +01:00
parent c4135308e6
commit a4f3aa5d6f
Signed by: simon
GPG key ID: 40E7A58C4AA1EDB2
23 changed files with 2424 additions and 152 deletions

View file

@ -1,7 +1,7 @@
# SMTP Configuration Concept
**Status:** Draft
**Last updated:** 2026-03-11
**Status:** Implemented
**Last updated:** 2026-03-12
---
@ -13,8 +13,8 @@ Enable configurable SMTP for sending transactional emails (join confirmation, us
## 2. Scope
- **In scope:** SMTP server configuration (host, port, credentials, TLS/SSL), test email from Settings UI, warning when SMTP is not configured in production.
- **Out of scope:** Changing how AshAuthentication or existing senders use the mailer; they keep using `Mv.Mailer` and `mail_from/0`. No separate "form_mail" config the existing **mail_from** (MAIL_FROM_NAME, MAIL_FROM_EMAIL) remains the single sender identity for all transactional mails.
- **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.
---
@ -31,71 +31,84 @@ When an ENV variable is set, the corresponding Settings field is read-only in th
## 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 | `SMTP_PASSWORD_FILE` | — | Docker/Secrets: path to file with password |
| TLS/SSL | `SMTP_SSL` or similar | `smtp_ssl` | e.g. `tls` / `ssl` / `none` (default: tls)|
| 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 |
**Sender (unchanged):** `mail_from` stays separate (`MAIL_FROM_NAME`, `MAIL_FROM_EMAIL` in ENV; no DB fields for from-address).
**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` and `TOKEN_SIGNING_SECRET_FILE` in `runtime.exs`. Read once at runtime when building mailer config; ENV `SMTP_PASSWORD` overrides file if both are set (or define explicit precedence and document it).
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 (e.g. no host):
- Keep using the default adapter (e.g. Local) or a no-op adapter so the app does not crash.
- **Show a clear warning in the Settings UI** (SMTP section): e.g. "SMTP is not configured. Transactional emails (join confirmation, password reset, etc.) will not be delivered reliably." and optionally list consequences (no join confirmations, no password resets, etc.).
- Log a warning at startup or when sending is attempted if SMTP is not configured in prod.
- **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 (same page as OIDC, Vereinfacht).
- **Elements:**
- Input: **recipient email address** (required for sending).
- Button: **"Send test email"** (or similar).
- **Behaviour:** On click, send one simple transactional-style email to the given address (subject and body translatable via Gettext, e.g. "Mila Test email" / "This is a test."). Use current SMTP config and `mail_from`.
- **Feedback:** Show success message or error (e.g. connection refused, auth failed, invalid address). Reuse the same UI pattern as Vereinfacht "Test Integration" (result assign, small result component with success/error states).
- **Permission:** Reuse existing Settings page authorization (admin); no extra check for the test-email action.
- **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. Implementation Hints
## 8. Sender Identity (`mail_from`)
- **Config module:** Extend `Mv.Config` with `smtp_*` helpers (e.g. `smtp_host/0`, `smtp_port/0`, …) using `env_or_setting/2` and, for password, ENV vs `SMTP_PASSWORD_FILE` vs Settings (sensitive).
- **runtime.exs:** When SMTP is configured (e.g. host present), set `config :mv, Mv.Mailer, adapter: Swoosh.Adapters.SMTP, ...` with the merged options. Otherwise leave adapter as in base config (Local in dev, Test in test, and in prod either Local with warning or explicit "not configured" behaviour).
- **Setting resource:** New attributes: `smtp_host`, `smtp_port`, `smtp_username`, `smtp_password` (sensitive), `smtp_ssl` (string or enum). Add to create/update `accept` lists and to seeds if needed.
- **Migration:** Add columns for the new Setting attributes.
- **Test email:** New function (e.g. `Mv.Mailer.send_test_email(to_email)`) returning `{:ok, _}` or `{:error, reason}`; call from LiveView event and render result in the SMTP section.
`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. Documentation and i18n
## 9. AshAuthentication Senders
- **Gettext:** Use Gettext for test email subject and body and for all new Settings labels/hints (including the "SMTP not configured" warning).
- **Docs:** Update `CODE_GUIDELINES.md` (e.g. §3.11 Email) and deployment/configuration docs to describe ENV and Settings for SMTP and the test email. Add this feature to `docs/feature-roadmap.md` (e.g. under Admin Panel & Configuration or Communication).
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. Summary Checklist
## 10. TLS / SSL in OTP 27
- [ ] ENV: `SMTP_HOST`, `SMTP_PORT`, `SMTP_USERNAME`, `SMTP_PASSWORD`, `SMTP_PASSWORD_FILE`, `SMTP_SSL` (or equivalent).
- [ ] Settings: attributes and UI for host, port, username, password, TLS/SSL; ENV-override hints.
- [ ] Password from file: `SMTP_PASSWORD_FILE` supported in runtime config.
- [ ] Mailer: Swoosh SMTP adapter configured from merged ENV + Settings when SMTP is configured.
- [ ] Prod warning: clear message in Settings when SMTP is not configured, with consequences.
- [ ] Test email: button + recipient field, translatable content, success/error display; existing permission sufficient.
- [ ] Gettext for new UI and test email text.
- [ ] Feature roadmap and code guidelines updated.
OTP 26+ enforces `verify_peer` by default, which fails for self-signed or internal SMTP server certificates.
Both `tls_options: [verify: :verify_none]` (for STARTTLS, port 587) and `sockopts: [verify: :verify_none]` (for direct SSL, port 465) are set in `Mv.Mailer.smtp_config/0` to allow such certificates.
For ENV-based boot config, the same options are set in `config/runtime.exs`.
---
## 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.