Transactional email¶
Minder can send a small set of transactional emails: self-service password reset links, and security notices such as "your password was changed". Email is off by default. This page follows the operator's path from "do I need this?" to running it in production.
At a glance
- Off by default (
EMAIL_BACKEND=none). Without it, an install works exactly as it did before email existed. - Production backend:
smtp, through your own relay or a mail provider. - Configuration lives in the root
./.env. Changes take effect only when the api-gateway container is recreated (see Apply the change). - Operator tooling:
bash setup.sh email .... Health: theemailobject in the api-gateway/health.
1. Decide whether you need email¶
Without email (EMAIL_BACKEND=none), everything works except the features
below. Sign-in, SSO, chat, knowledge bases, plugins and administration are not
affected. Password recovery is done by an administrator:
- through the admin reset endpoints and the Users page (see Authentication), or
- as break-glass from the host:
bash setup.sh user reset-password <username>. It prints a temporary password once and ends every session for that account.
With email, these turn on:
| Feature | What the user sees |
|---|---|
| Self-service password reset | A Forgot password? link on the sign-in page. It sends a one-time reset link to local accounts. |
| Notices for SSO accounts | An SSO-linked account that asks for a reset gets a notice that points to its identity provider. It never gets a reset link. |
| Security notices | A "your password was changed" email on every password path: self-service reset, change-password and administrator resets. A "your sessions were ended" email when sessions are revoked without a password change. |
Email is sent from the api-gateway process. A background worker drains a database outbox, so no request ever waits on SMTP.
SaaS deployments: fail-fast
With SAAS_MODE=true, the gateway refuses to start (there is no
override) when any of these is true:
EMAIL_BACKENDisnone,logorcapture.EMAIL_FROMis empty.SMTP_SECURITY=none.MINDER_CLIENT_BASE_URLis not anhttpsURL, or is still the shipped default.PASSWORD_RESET_EMAIL_FOR_PLATFORM_ADMINS=trueorADMIN_RESET_TEMP_PASSWORD_WITH_EMAIL=true.
The check covers configuration only. If the SMTP relay is unreachable, that shows up in readiness and never crash-loops the gateway.
2. Choose a backend¶
EMAIL_BACKEND |
Use it for | Behaviour |
|---|---|---|
none (default) |
Installs without email | Nothing is sent. The reset routes are not mounted (404), and Forgot password? is hidden. |
smtp |
Production | Sends through an SMTP relay or provider. |
log |
Local development | Writes each rendered email, reset links included, to the api-gateway log at INFO. The gateway logs a warning at startup. Never use it on a host with real users. |
capture |
Automated tests | Keeps messages in process memory, so in practice nothing arrives anywhere. |
log and capture are refused in SaaS mode.
Picking an SMTP relay. Any SMTP relay or transactional-mail provider works: your organization's mail server, a cloud provider's SMTP interface, or a dedicated transactional-email service. You need:
- a hostname and port,
- a username and password (or an API key used as the password), unless the relay authenticates by IP address,
- permission to send as the address you will put in
EMAIL_FROM. Most providers require you to verify the sending domain first.
Choose the TLS mode to match the port:
| Port | SMTP_SECURITY |
Meaning |
|---|---|---|
| 587 | starttls (default) |
Plain connection upgraded with STARTTLS. If the server does not offer STARTTLS, the send fails. Minder never falls back to plaintext. |
| 465 | tls |
TLS from the first byte ("implicit TLS" / SMTPS). |
| 25 (or a local relay) | none |
Plaintext, no certificate check. Self-host only, and only for a relay on a trusted network (for example, one on the same host). The gateway logs a warning at startup, and SaaS mode refuses it. |
With starttls and tls, the relay's certificate is verified against the
container's system trust store and must match SMTP_HOST. There is no setting
for a private CA, so a relay with a self-signed certificate fails with a tls
error.
Tip
Many cloud and residential networks block outbound port 25. Use 587 or 465 to reach an external provider.
3. Configure¶
All settings go in the root ./.env, the only file you edit (see
Production). .env.example has a
commented TRANSACTIONAL EMAIL block you can uncomment.
A complete working example¶
# --- Transactional email -----------------------------------------------------
EMAIL_BACKEND=smtp
EMAIL_FROM=Minder <no-reply@mail.example.com>
EMAIL_REPLY_TO=support@example.com # optional
EMAIL_PRODUCT_NAME=Minder # the only branding in templates
SMTP_HOST=smtp.provider.example
SMTP_PORT=587
SMTP_SECURITY=starttls
SMTP_USERNAME=minder-relay-user
SMTP_PASSWORD=change-me # a secret: .env only, never commit
# Links in emails are built from this. It must be the address users open
# the client at, as they reach it.
MINDER_CLIENT_BASE_URL=https://minder.example.com
# --- Password reset (on by default once email is on) ------------------------
PASSWORD_RESET_EMAIL_ENABLED=true
PASSWORD_RESET_TOKEN_TTL_MINUTES=30
# SSO_PASSWORD_HELP_URL=https://idp.example.com/password-help
MINDER_CLIENT_BASE_URL matters
Reset links are <MINDER_CLIENT_BASE_URL>/reset-password#token=..., built
only from this setting and never from a request's Host header. If it still
has the default (https://client.minder.local), external users get links
that do not resolve.
$ in passwords
Docker Compose interpolates $ in .env values. If SMTP_PASSWORD
contains $, write it as $$.
All settings¶
| Key | Default | Allowed / notes |
|---|---|---|
EMAIL_BACKEND |
none |
none, log, capture, smtp |
EMAIL_FROM |
(empty) | Required for any backend other than none. Name <addr@domain> or a bare address. Its domain is the one that needs sender DNS. |
EMAIL_REPLY_TO |
(empty) | Optional Reply-To header. |
EMAIL_PRODUCT_NAME |
Minder |
1-64 characters on one line. The only branding templates render. |
EMAIL_DEFAULT_LOCALE |
en |
Fallback language. The templates ship in en and tr. |
EMAIL_MAX_PER_MINUTE |
60 |
1-10000. Global send ceiling. Excess mail waits in the outbox; it is not dropped. |
EMAIL_MAX_PER_RECIPIENT_PER_DAY |
10 |
1-1000. Rolling 24 h per address. Security notices are exempt. |
EMAIL_OUTBOX_PII_RETENTION_DAYS |
7 |
0 up to EMAIL_OUTBOX_RETENTION_DAYS. See Retention. |
EMAIL_OUTBOX_RETENTION_DAYS |
30 |
1-3650 |
SMTP_HOST |
(empty) | Required when EMAIL_BACKEND=smtp. |
SMTP_PORT |
587 |
1-65535 |
SMTP_USERNAME / SMTP_PASSWORD |
(empty) | Login is attempted only when SMTP_USERNAME is set. |
SMTP_SECURITY |
starttls |
starttls, tls, none |
SMTP_TIMEOUT_SECONDS |
10 |
Greater than 0 and at most 60. Applies per socket operation. |
PASSWORD_RESET_EMAIL_ENABLED |
true |
Self-service reset is available when email is on and this is true. |
PASSWORD_RESET_TOKEN_TTL_MINUTES |
30 |
15-60. Startup refuses any other value. |
PASSWORD_RESET_MAX_PER_HOUR |
3 |
Per account, at least 1. |
PASSWORD_RESET_MIN_INTERVAL_SECONDS |
60 |
Per account, not negative. |
PASSWORD_RESET_MIN_RESPONSE_MS |
400 |
0-10000. Response-time floor for reset requests. |
AUTH_EMAIL_IP_LIMIT_PER_MINUTE |
(empty) | Per-IP limit on the reset routes. Empty means the same as the auth rate limit. 0 turns the per-IP limit off on these routes only. |
SSO_PASSWORD_HELP_URL |
(empty) | An http(s) URL, used as the link in the SSO-account notice. When empty, the notice links to the identity provider's issuer URL. |
EMAIL_PLACEHOLDER_DOMAINS |
(empty) | Comma-separated domains that are never mailed. The SSO fallback placeholder domain is always included. |
PASSWORD_RESET_EMAIL_FOR_PLATFORM_ADMINS |
(unset) | When unset, it is false whenever Platform Admin 2FA is required (always in SaaS), and true otherwise. |
ACCOUNT_TOKEN_RETENTION_DAYS |
7 |
At least 1. |
An inconsistent value (an out-of-range number, smtp without SMTP_HOST, or a
backend without EMAIL_FROM) stops the gateway at startup with a message
that names the setting:
Apply the change¶
The backend is chosen once, at startup. The gateway also decides at startup
whether to mount the reset routes. Docker Compose bakes .env values into the
container when it creates it. A plain container restart therefore keeps the
old values, and you have to recreate the container:
bash setup.sh start # re-applies ./.env and recreates only the services whose configuration changed
# or the full cycle:
bash setup.sh restart # stop everything, then start
Warning
bash setup.sh restart api-gateway and docker compose restart api-gateway
restart the existing container with its old environment. They do
not pick up .env edits.
Then confirm that the gateway came up with the new backend:
docker logs minder-api-gateway 2>&1 | grep -i "transactional email backend"
# Transactional email backend: smtp
4. Sender DNS (SPF, DKIM, DMARC)¶
Receiving providers check that mail claiming to come from your domain was
authorized by that domain. Mail that fails these checks lands in spam or is
rejected outright, and large mailbox providers require them. You publish the
records on the domain of EMAIL_FROM (in the example, mail.example.com).
Using a dedicated subdomain for transactional mail keeps its reputation separate
from your staff mailboxes.
Minder does not sign mail itself. DKIM signing is done by your relay or provider, so the exact record values come from your provider's domain-setup page. The shapes are:
| Record | Name | Type | Example value | Purpose |
|---|---|---|---|---|
| SPF | mail.example.com |
TXT | v=spf1 include:<provider-spf-domain> -all |
Lists the servers allowed to send for the domain. A self-run relay uses ip4:<relay public IP> instead of include:. Publish one SPF record per name. |
| DKIM | <selector>._domainkey.mail.example.com |
TXT (or CNAME to the provider) | v=DKIM1; k=rsa; p=<public key> |
Lets receivers verify the relay's signature. The provider gives you the selector and key. |
| DMARC | _dmarc.mail.example.com |
TXT | v=DMARC1; p=none; rua=mailto:dmarc-reports@example.com |
Tells receivers what to do when SPF/DKIM do not align with the From domain, and where to send reports. Start with p=none, then move to quarantine or reject once the reports are clean. |
If you run your own relay, also give its public IP a reverse DNS (PTR) record that matches its hostname.
Check the records from any machine:
dig +short TXT mail.example.com # SPF: "v=spf1 ..."
dig +short TXT <selector>._domainkey.mail.example.com # DKIM: "v=DKIM1; ..."
dig +short TXT _dmarc.mail.example.com # DMARC: "v=DMARC1; ..."
# Windows / no dig:
nslookup -type=TXT _dmarc.mail.example.com
DNS changes can take up to the record's TTL to become visible. To confirm that receivers actually accept the mail, send a test message (next step) to:
- a mailbox at a large provider, then open the message's original headers and
look for
Authentication-Results: ... spf=pass ... dkim=pass ... dmarc=pass, or - an online deliverability checker that gives you a one-off address (for example mail-tester.com). An SPF/DKIM/DMARC lookup tool such as MXToolbox can also validate the published records.
5. Verify¶
Send a test message¶
The command runs inside the api-gateway container and uses its configuration. It sends directly through the backend and bypasses the outbox, rate limits and the suppression list, so you get the result at once:
| Output | Meaning |
|---|---|
ok: accepted by the smtp backend (to y***@e***.com) |
The relay accepted the message. Now check the inbox and the headers (see above). |
transient failure: auth (to ...) |
The relay rejected the credentials. |
transient failure: tls (to ...) |
TLS handshake or certificate failure, or STARTTLS not offered. |
transient failure: connection / timeout |
Host or port unreachable, or blocked by a firewall. |
permanent failure: smtp_5xx / recipient_rejected |
The relay refused the message or the recipient. |
Email is disabled (EMAIL_BACKEND=none); nothing to test. |
The container still runs with email off. See Apply the change. |
api-gateway container not running |
Start the stack first. |
Addresses are always shown masked. See Troubleshooting for fixes.
Outbox status¶
Add --purpose password_reset to filter by purpose, or pass a row id to see one
message's metadata (purpose, locale, status, attempts, last error class,
deadlines, masked recipient).
The /health email object¶
A healthy SMTP setup:
{
"backend": "smtp",
"status": "ok",
"outbox_pending": 0,
"oldest_pending_age_seconds": 0,
"dead_last_24h": 0
}
| Field | Healthy | Notes |
|---|---|---|
backend |
smtp |
What the running process actually loaded. |
status |
ok |
disabled when EMAIL_BACKEND=none. degraded when the backend check failed: the gateway could not connect, negotiate TLS, log in, or get a reply to NOOP from the relay. The check is cached for 60 s, and /health never waits more than about 2 s for it. |
outbox_pending |
small, near 0 | Messages waiting or in flight. |
oldest_pending_age_seconds |
well under 300 | A growing value means mail is not leaving. |
dead_last_24h |
0 |
Messages that gave up in the last 24 h. |
The object is informational: it never changes the gateway's own /health
status, so a relay outage does not mark the gateway unhealthy. The object never
contains the relay host, the username, the password, EMAIL_FROM or any
address. The counters are null only when the database could not be read.
Alerts and metrics¶
The api-gateway exports Prometheus metrics, including
minder_email_sent_total, minder_email_failed_total, minder_email_dead_total,
minder_email_expired_total, minder_email_skipped_total,
minder_email_outbox_pending, minder_email_outbox_oldest_pending_age_seconds
and minder_password_reset_requests_total{outcome}.
An email rule group ships in Prometheus's alerts.yml. Every rule carries
component="email", so you can route them with your existing Alertmanager
receivers:
| Alert | Fires when | Severity |
|---|---|---|
EmailOutboxLag |
The oldest pending message has waited more than 5 min, for 10 min. | warning |
EmailOutboxLagSaaS |
The same condition in SaaS mode. | critical |
EmailDeadLetters |
Any message was dead-lettered in the last 15 min. | warning |
EmailSendFailureRatioHigh |
More than 20 % of send attempts failed over 15 min. | warning |
Make sure your Prometheus loads those rules: its rule_files must match where
alerts.yml is mounted in the container. Confirm that the group is loaded:
If that prints nothing, the email alerts are not active. See Monitoring.
6. Turn on password reset¶
Self-service reset is on as soon as email is on: PASSWORD_RESET_EMAIL_ENABLED
defaults to true. To run email for notices only, set it to false and
apply the change.
Check what the sign-in page offers:
curl -s http://localhost:8000/v1/auth/capabilities
# {"password_reset_email":true,"email_verification":false,"registration_mode":"invite"}
The client shows Forgot password? only when password_reset_email is
true. When it is false, the reset routes return 404.
The user's flow:
- The user selects Forgot password? on the sign-in page and enters their email address.
- Whatever they entered, they see the same confirmation: "If an account exists for that address, we've sent instructions."
- A local account receives a link to
/reset-password. The link is valid forPASSWORD_RESET_TOKEN_TTL_MINUTES(15-60, default 30), counted from when the email is sent. - The user sets a new password (at least 8 characters). Every existing session for the account ends, and the user signs in again with the new password. They also get a "your password was changed" notice.
What other accounts get:
| Account | Result |
|---|---|
| SSO-linked | A notice that its password is managed by the identity provider, linking to SSO_PASSWORD_HELP_URL (or the IdP's issuer URL). No reset link. |
| Platform Admin, while reset by email is off for admins | A notice that points to the break-glass bash setup.sh user reset-password on the host. No reset link. |
| Deactivated | Nothing. |
| Address on the suppression list, a placeholder domain, or an invalid address | Nothing. |
| An address shared by several accounts (after normalization) | Nothing. List them with bash setup.sh users email-collisions. |
| No account | Nothing. |
Each of these outcomes is visible only to operators, through the audit log
(auth.password_reset.* entries) and the
minder_password_reset_requests_total{outcome} metric. The requester always
gets the same answer.
7. Operate¶
The outbox and retries¶
Every email is first written to an outbox table in the same database transaction as the change that triggered it. A rolled-back change sends nothing, and a committed one is guaranteed an attempt. Each api-gateway replica runs one worker. Workers claim rows with a lease, so replicas never send the same row twice.
| Outcome | What happens |
|---|---|
| Accepted by the relay | sent |
| Transient failure (SMTP 4xx, connection, timeout, TLS, authentication) | Retried with exponential backoff and jitter (about 30 s, 2 min, 8 min, 30 min, then at most 1 h between tries), up to 8 attempts. |
| Permanent failure (SMTP 5xx, invalid address, template error) | dead at once |
Recipient rejected during the SMTP conversation (550/551/553 on RCPT TO) |
dead, plus an automatic hard_bounce suppression. Policy rejections (5.7.x, for example "relay access denied") are treated as a plain 5xx and do not suppress the recipient. |
| Past its delivery deadline | expired |
| Recipient suppressed, or the reset link superseded | cancelled |
Authentication failures are retried, not dead-lettered. While you fix
credentials, queued mail waits instead of being thrown away, and
EmailSendFailureRatioHigh / EmailOutboxLag tell you something is wrong.
Delivery deadlines. A message is useful only for a limited time: 15 min for reset links and SSO/admin notices, and 24 h for security notices. After that, it expires instead of arriving late.
Dead letters: retry and replay¶
bash setup.sh email outbox status # how many are dead?
bash setup.sh email outbox status <id> # why? (last_error_class)
bash setup.sh email outbox retry <id> # requeue one row
bash setup.sh email outbox replay --status dead # requeue all dead rows
bash setup.sh email outbox replay --status dead --purpose password_changed --since 2026-10-01T00:00:00
Requeued rows get a fresh attempt budget. Output looks like:
Rows are skipped when they are not dead, are past their delivery deadline, or have already had their address removed by retention. Because reset links expire after 15 min, a dead reset email usually cannot be replayed. Fix the cause, then ask the user to request a new link. Retries and replays are audited.
Suppressions¶
A suppressed address gets no mail: new messages to it are recorded as
cancelled/suppressed, and a reset request for it sends nothing. Addresses are
added:
- automatically (
hard_bounce, sourcesmtp_sync) when the relay rejects the recipient during sending; - manually (
manualorcomplaint, sourcecli), for example after a spam complaint.
Only rejections during the SMTP conversation are detected. Bounce messages that your relay or provider sends back later are not processed, so handle those in your provider's dashboard or add them manually.
bash setup.sh email suppressions list
# 3f9a0c41d2b7e865 hard_bounce smtp_sync 2026-10-02T09:14:03+00:00
bash setup.sh email suppressions add user@example.com # reason: manual
bash setup.sh email suppressions add user@example.com --reason complaint
bash setup.sh email suppressions remove user@example.com
# Unsuppressed u***@e***.com. (or: "... is not suppressed; nothing changed.")
The list stores only a SHA-256 hash of the normalized address (trimmed, Unicode
NFKC, lower-cased), so list shows a hash prefix and never the address. To
check whether a specific address is in the list without changing anything:
Remove a suppression only after the underlying problem is fixed, for example after a mailbox that did not exist has been created. Changes are audited.
Retention¶
Retention runs about hourly in the worker, even when email is off:
sent,expiredandcancelledrows have their address and template parameters clearedEMAIL_OUTBOX_PII_RETENTION_DAYS(7) days after they finish;deadrows keep a masked address (u***@e***.com) after that window, so you can still investigate them;- every finished row is deleted
EMAIL_OUTBOX_RETENTION_DAYS(30) days after it finishes; - reset tokens are deleted
ACCOUNT_TOKEN_RETENTION_DAYS(7) days after they expire or are used.
Turning email off again¶
If the gateway starts with EMAIL_BACKEND=none, it cancels every message
still queued (last_error_class=backend_disabled) and invalidates their reset
links. If you turn email on again later, old links can never arrive late. The
gateway logs:
8. Troubleshooting¶
Start with bash setup.sh email send-test <you>. It reports the error class
directly. Logs carry row ids, purposes and error classes, never addresses or
links: docker logs minder-api-gateway 2>&1 | grep -i email.
| Symptom | Likely cause | Fix |
|---|---|---|
Gateway won't start; log says Refusing to start, email configuration: ... |
A missing or out-of-range setting, or a value refused in SaaS mode | Fix the named key in .env and apply the change. |
transient failure: auth; EmailSendFailureRatioHigh fires; outbox pending grows |
Wrong SMTP_USERNAME/SMTP_PASSWORD, an API key used as the wrong field, a $ in the password that was not escaped, or the provider needs the sender verified |
Correct the credentials and apply the change. The queued mail is retried automatically. |
transient failure: tls |
Wrong mode for the port (starttls on 465, or tls on 587), a relay without STARTTLS, a self-signed or private-CA certificate, or SMTP_HOST not matching the certificate name |
Use 587 with starttls or 465 with tls, set SMTP_HOST to the name on the certificate, and use a relay with a publicly trusted certificate. |
transient failure: connection / timeout |
Firewall, outbound port block (often port 25), wrong host | Test from the host (nc -vz smtp.provider.example 587) and switch to 587/465. |
permanent failure: smtp_5xx |
The relay refuses the sender (domain not verified, relay access denied) | Verify the EMAIL_FROM domain with the provider, or allow this host on your relay. |
send-test says ok, but nothing arrives |
Spam folder, provider-side hold, or a rejection after the relay accepted the message | Check spam and the provider's activity log. Check the sender DNS records. |
| Mail lands in spam | SPF/DKIM/DMARC missing or not aligned with the EMAIL_FROM domain, or a new domain/IP with no reputation |
Publish all three records, confirm dmarc=pass in the headers, and use a domain you control in EMAIL_FROM. |
/health shows "backend": "none" after you configured SMTP |
The container still runs with the old environment | Recreate it: bash setup.sh start (not restart api-gateway). |
Messages cancelled with backend_disabled |
The gateway started once with email off (for example, before .env was applied) |
Expected. Fix the configuration and ask users to request a new reset link. |
EmailOutboxLag fires, status is ok |
Sends are throttled by EMAIL_MAX_PER_MINUTE, or retries are backing off |
Check email outbox status. Raise EMAIL_MAX_PER_MINUTE if your provider allows it. |
Reset requests return 429 |
The per-IP limit on the reset routes. Many users behind one NAT can hit it. | Raise AUTH_EMAIL_IP_LIMIT_PER_MINUTE, or set it to 0 to rely on the per-account and per-recipient limits. |
| A user didn't get a reset email | See the checklist below |
A user didn't get a reset email:
curl -s http://localhost:8000/v1/auth/capabilities: ispassword_reset_emailtrue?- Check the audit log for that account's latest
auth.password_reset.*entry:requested: a link was queued. Continue with step 3.sso_notice/privileged_refused: the user got a notice instead of a link.undeliverable: a suppressed, placeholder or invalid address.rejectedwith reasonaccount_inactive: the account is deactivated.suppressedwith reasonaccount_limitorrecipient_cap: the user asked too often. The limits are 3 per hour and one per 60 s per account, and 10 per address per day.- No entry at all: no account matched that address, or the address is
shared by several accounts (
bash setup.sh users email-collisions).
bash setup.sh email outbox status --purpose password_reset: look fordead(inspect withoutbox status <id>) orexpired. An expired reset email was not delivered within 15 min, usually because of a relay outage.- Check the user's spam folder.
- In every case, the user can simply request a new link once the cause is fixed. An administrator can also reset the password directly (see Authentication).
9. Security notes¶
- No account enumeration. The reset request always returns
202with the same body, whether or not the address has an account, is SSO-linked, deactivated, suppressed or rate-limited. The response is padded toPASSWORD_RESET_MIN_RESPONSE_MSplus random jitter, so the branch that queues mail is not measurably slower. Only malformed input (422) and the per-IP limit (429) differ, and neither depends on the account. Rate limits skip silently for the same reason. - Short-lived, single-use tokens. The reset secret is created right before
the email is sent. It is valid for
PASSWORD_RESET_TOKEN_TTL_MINUTES(15-60 min) and works only once. It is also bound to the account's current password: any password change invalidates outstanding links, and a successful reset voids the account's other reset links. Only a hash of the token is stored. - Link handling. The token travels in the URL fragment
(
#token=...), so it never reaches server access logs or aRefererheader. The client submits it with an explicit POST. Every token failure returns the same400 invalid_or_expired_token. - A reset ends all sessions. A completed reset revokes every existing session for the account, on every device, and does not sign the user in. The account owner gets a "password changed" notice, so an unexpected reset is noticed.
- SSO and privileged accounts get no links. SSO-linked accounts are pointed to their identity provider. With Platform Admin 2FA required (always in SaaS), Platform Admins get a notice instead of a link, and their recovery is the host-side break-glass command.
- No secrets or addresses in output. Logs, CLI output, audit metadata and
/healthshow masked addresses only. Thelogbackend is the one deliberate exception, which is why it is for development only. KeepSMTP_PASSWORDin.env(mode600), as with every other secret; see Security.
Related pages¶
- Production: go-live checklist and operations.
- Authentication: accounts, admin password resets, sessions.
- Security: secrets handling and hardening.
- Monitoring: Prometheus, alert rules, Alertmanager receivers.