Plans & features
Every plan in /admin/plans carries a set of quantitative limits and binary feature flags. They are consumed everywhere via plan_limit($con, $user_id, $name) and plan_feature($con, $user_id, $name) — never check fields directly so the Free fallback works.
Reference
| Feature | Type | What it does |
|---|---|---|
max_items_per_type | Int or null (unlimited) | Cap on accounts, cards, banks, notes, etc. per type. Free is typically 10. |
max_storage_mb | Int or null | Total size cap for /documents. |
max_scans_per_month | Int or null | How many times the user can click "Scan vault" per month. Resets on calendar month change. |
scan_history_visible | Bool | If true, /vault-health shows the table with the last 20 scans. Off → shows an upsell message. |
max_emergency_contacts | Int or null | Cap on emergency contacts. 0 = feature blocked (tab shows upsell). |
max_devices | Int or null | Concurrent active sessions. Exceeding shows "Too many devices" modal. |
monthly_summary_email | Bool | If on, on day 1 of every month the user gets an email with the health score + leaked / reused / weak counts. Sent only if ≥ 1 prior scan. |
breach_alerts_basic | Bool | Public digest: daily cron consults HIBP /api/v3/breaches and emails the user the list of services whose breach was published in the last 24h. Does not compare against the vault — only informs of recent leaks so the user reacts if they had an account on any. |
breach_alerts_realtime | Bool | Personalised scan: when the user enters the app with the vault unlocked, if ≥ 24h have passed since the last auto-scan, one is fired in the background. If new compromised passwords show up vs the previous scan, an instant email is sent. Requires vault_key in PHP session to decrypt — can't run from a pure cron without breaking zero-knowledge. |
How the two alerts combine
| Plan with | Public digest email (daily cron) | Personalised email after auto-scan |
|---|---|---|
Neither basic nor realtime | ❌ | ❌ |
Only basic | ✅ | ❌ |
Only realtime | ✅ (included as courtesy) | ✅ |
basic + realtime | ✅ | ✅ |
Realtime "includes" basic: if a plan pays the high tier, it gets both alert types. The natural upsell is Free → Premium (basic) → Pro (basic + realtime).
User toggle wins
If the plan allows alerts but the user turns off the "Breach alerts" toggle in /account-and-security, no emails are sent (neither digest nor personalised). The toggle is the final decision.
Plan resolution
The user's effective plan is computed by user_plan($con, $user_id):
- Look up
user_subscriptionswithstatus = 'active'(ortrialingwithin trial days). - If found, return that plan.
- If none active, look at
users.plan_id(admin-assigned). - Otherwise fall back to the plan with
is_default = 1. - If no default exists either, fall back to Free (a synthetic plan with conservative limits).
Every call site that needs a limit goes through plan_limit() / plan_feature() so the Free fallback is uniform.