Multi-tenancy
Mailgrid is multi-tenant from the ground up. Every record carries a tenant_id; every query is parameterized.
The model
- A tenant represents one customer organization.
- Each tenant has many API keys, scoped to subsets of capabilities.
- Each tenant has independent rate limits, suppressions, streams, contacts, templates, files, and analytics.
- SES identities are account-wide on AWS — Mailgrid tracks ownership via the
domainstable and enforces the From-address policy at the IAM level.
Isolation guarantees
| Resource | Isolation |
|---|---|
| D1 tables | Parameterized WHERE tenant_id = ? on every query. No global tables. |
| R2 storage | Keys prefixed tenants/{tenant_id}/… |
| Rate limiting | One Durable Object instance per tenant. No tenant can starve another. |
| Suppressions | Per-tenant. The same email can be suppressed for tenant A but valid for tenant B. |
| From-address | IAM policy on the SES IAM user restricts From: to verified domains. |
Creating tenants
Mailgrid currently provisions tenants via the bootstrap script:
node scripts/bootstrap-tenant.mjs \ --name "Acme Inc." \ --email "admin@acme.com" \ --hmac-secret "$API_KEY_HMAC_SECRET" \ > /tmp/seed.sql wrangler d1 execute inboxos --remote --file=/tmp/seed.sql
The script creates a tenant row and an admin API key with all scopes. Save the key — only its HMAC hash is stored.
Plans
Each tenant carries a plan field — one of free, pro, scale, business, enterprise. The default rate_limit_per_minute is 600; override per-tenant via the database:
wrangler d1 execute inboxos --remote --command \ "UPDATE tenants SET rate_limit_per_minute = 3000 WHERE id = '...'"