7 KiB
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-submitform. - 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:
MAIL_FROM_NAME/MAIL_FROM_EMAILENV variablessmtp_from_name/smtp_from_emailin Settings (DB)- 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(or1/yes) when configuring SMTP via environment variables inconfig/runtime.exs. This setsconfig :mv, :smtp_verify_peerand 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
- ENV:
SMTP_HOST,SMTP_PORT,SMTP_USERNAME,SMTP_PASSWORD,SMTP_PASSWORD_FILE,SMTP_SSL. - ENV:
MAIL_FROM_NAME,MAIL_FROM_EMAILfor sender identity. - Settings: attributes and UI for host, port, username, password, TLS/SSL, from-name, from-email.
- Password from file:
SMTP_PASSWORD_FILEsupported inruntime.exs. - Mailer: Swoosh SMTP adapter configured from merged ENV + Settings when SMTP is configured.
- Per-request SMTP config via
Mv.Mailer.smtp_config/0for Settings-only scenarios. - TLS certificate validation relaxed for OTP 27 (tls_options + sockopts).
- Prod warning: clear message in Settings when SMTP is not configured.
- Test email: form with recipient field, translatable content, classified success/error messages.
- AshAuthentication senders: graceful error handling (no crash on delivery failure).
- Gettext for all new UI strings, translated to German.
- Docs and code guidelines updated.
12. Follow-up / Future Work
- SMTP password at-rest encryption: The
smtp_passwordattribute is currently stored in plaintext in thesettingstable. It is excluded from default reads (same pattern asoidc_client_secret); both are read only via explicit select when needed. For production systems at-rest encryption (e.g. with 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_smtperror tuples first where possible, and fall back to string analysis only when needed. Server wording varies; consider extending patterns as new providers are used.