Self-Hosted vs Commercial
Inboxes runs in one of two modes, determined by a single environment variable at startup.
How mode is determined
STRIPE_KEYis set and non-empty — Commercial mode (billing, email verification, open signup)STRIPE_KEYis unset or empty — Self-hosted mode (no billing, no email verification, invite-only after initial setup)
Self-hosted mode
Setup wizard
On first launch with zero users in the database, the setup wizard is available:
GET /api/setup/status— returnsneeds_setup: truewhen zero users existPOST /api/setup/validate-key— validates a Resend API key and returns available domainsPOST /api/setup— creates the first org and admin user
After setup, a JWT cookie is set automatically so the admin proceeds directly to onboarding.
Invite-only after setup
After the first user exists, new users can only be added via POST /api/users/invite (requires admin role). Both the setup wizard and the signup endpoint are locked.
No email verification
In self-hosted mode, all users are created with email_verified = true. There is no verification code step.
System email configuration
The instance owner can manage system email settings through owner-only endpoints:
GET /api/system/email— returns current from-address and from-namePATCH /api/system/email— updates settings, with optional test email
Auto-poll (inbox poller)
Self-hosted instances that cannot receive webhooks (e.g., behind NAT) can enable automatic polling:
| Setting | Default | Range |
|---|---|---|
auto_poll_enabled | false | — |
auto_poll_interval | 300 (seconds) | 120–3600 |
The inbox poller checks every 30 seconds for orgs due for polling, fetching up to 10 pages (100 emails per page) and stopping when it encounters an already-imported email.
Commercial mode
Open signup with email verification
In commercial mode, signup is open to anyone. New users receive a 6-digit verification code by email and must verify before logging in.
Plan enforcement
Feature endpoints are gated by RequirePlan middleware:
proandpast_dueplans are allowed throughcancelledplans with a futureplan_expires_atare allowed (grace period)- Returns
402 "subscription_required"otherwise - In self-hosted mode, this middleware is a no-op
Feature comparison
| Behavior | Self-Hosted | Commercial |
|---|---|---|
| Mode trigger | STRIPE_KEY absent | STRIPE_KEY present |
| First-run setup | Setup wizard or first-user signup | Standard signup |
| Signup after first user | Disabled (403) | Open registration |
| Email verification | Skipped | Required (6-digit code) |
| Billing / Stripe | No plan enforcement | Active plan required |
| System email settings | Owner-configurable via UI | Managed via env vars only |
| Auto-poll settings | Available in org settings | Hidden |
/api/config endpoint
GET /api/config is a public, unauthenticated endpoint that returns runtime configuration for the frontend. Cached for 1 hour.
{
"api_url": "https://mail.yourdomain.com",
"ws_url": "wss://mail.yourdomain.com",
"commercial": false
}The frontend uses commercial to conditionally show billing UI, signup forms, and verification flows.