Email pipeline
Every email has a template at app/email_defaults/<name>.body.html and <name>.subject.txt. The admin can override either of them in /admin/settings#templates. Placeholders are substituted via render_email_template().
Available emails
| Trigger | Template | Gates | |
|---|---|---|---|
login | Successful login (incl. social) | login | users.notifySessions = 1 |
loginCode | Email passwordless: send 6-digit code | loginCode | always (rate-limited separately) |
reset | "Forgot my PIN" → reset link | reset | always |
monthly | Day 1 of the month (cron) | monthly | notifyMonthly=1 + plan_feature(monthly_summary_email) + ≥1 prior scan |
breachAlert | After a manual scan with new compromised passwords | breachAlert | notifyBreaches=1 + plan_feature(basic OR realtime) |
breachDigest | Daily cron, if HIBP published new breaches | breachDigest | notifyBreaches=1 + plan_feature(basic OR realtime) |
emergency_invite | Admin designates an emergency contact | emergency_invite | — |
emergency_request | Contact requests access | emergency_request | Sent to the vault owner |
emergency_granted | Wait period elapsed without denial → contact receives the encrypted recovery code | emergency_granted | Sent to the contact |
emergency_denied | Owner denies the request manually | emergency_denied | Sent to the contact |
All emails are wrapped in a shared HTML layout (wrap_email_html()) that adapts to light/dark via CSS media queries.
Email i18n
Every send_*_email() function wraps its body in with_lang($recipient_lang, callable). This temporarily swaps $config['LANG_CODE'] so the template is rendered in the recipient's preferred language, regardless of which language the originating request happens to be in.
Subjects and bodies are also listed in translatable_settings_list(), so when the admin saves a template, translate_now() auto-translates it to every installed language. See i18n & auto-translation for the full pipeline.
Sending method
Configured in /admin/settings#email:
- Native — PHP's
mail(). Simplest, but requires the server to have a working MTA. - SMTP — host, port, secure (TLS/SSL/none), user, password. Custom lightweight client — no PHPMailer dependency.
The sender address and display name are set globally. A "Send test" button delivers a synthetic email to the admin to verify the config.
Defense in depth: gates at send time
Every email gate is rechecked at the moment of sending. Even if the user has a toggle ON, the sender re-evaluates plan_feature() — an email the plan no longer allows is never delivered. This protects against the "I downgraded but the toggle stayed on" race.