Authorization¶
Authentication tells us who you are. Authorization tells us what you can do. Mailyte uses a multi-tenant model where every piece of data belongs to an organization, and access is strictly scoped.
Multi-Tenant Isolation¶
graph TD
KEY1[API Key: Org A] --> ORGA[Organization A]
KEY2[API Key: Org B] --> ORGB[Organization B]
KEY3[API Key: Global] --> ORGA
KEY3 --> ORGB
ORGA --> DA[Domains A]
ORGA --> MA[Mailboxes A]
ORGA --> LA[Logs A]
ORGB --> DB[Domains B]
ORGB --> MB[Mailboxes B]
ORGB --> LB[Logs B] How Isolation Works¶
Every database table that holds tenant data includes an organization_id column. Every query is filtered by this column.
When an API key is scoped to an organization:
GET /api/v1/get/domain/allonly returns domains for that orgPOST /api/v1/add/mailboxcan only create mailboxes under that org's domainsGET /api/v1/get/tracking/stats/...only shows that org's tracking data- Queue, logs, analytics — all scoped to the org
There's no API endpoint that returns data across organizations unless you're using a global (unscoped) key.
Database-Level Enforcement¶
Every tenant-scoped query includes the org filter:
-- API key scoped to "acme-corp"
SELECT * FROM domains WHERE organization_id = 'acme-corp' AND active = 1;
-- Even if you pass a domain_id from another org, it returns nothing
SELECT * FROM email_accounts WHERE domain_id = 42 AND organization_id = 'acme-corp';
-- Returns 0 rows if domain 42 belongs to a different org
This is enforced at the application layer. The API never constructs a query without the org filter when the key is scoped.
Access Levels¶
Global Admin¶
- Uses
X-Admin-Passwordheader - Full access to everything
- Can create API keys, manage all organizations
- Not scoped to any organization
- Should only be used for system administration
Global API Key¶
- API key with
organization_id = NULL - Can access all organizations
- Used for management dashboards, billing integrations
- Should be very carefully protected
Organization-Scoped API Key¶
- API key with
organization_id = "some-org" - Can only access that organization's data
- Can create/read/update/delete domains, mailboxes, aliases within the org
- Cannot see other organizations or their data
- This is what you give to customers
Read-Only API Key¶
- API key with
read_only = true - Can only call GET endpoints
- Cannot create, update, or delete anything
- Useful for monitoring dashboards and reporting
Permission Model¶
The permissions JSON field on API keys controls granular access:
{
"permissions": [
"domain:read",
"domain:write",
"mailbox:read",
"mailbox:write",
"alias:read",
"alias:write",
"stats:read",
"tracking:read",
"webhook:read",
"webhook:write"
]
}
Permission Types¶
| Permission | Grants |
|---|---|
domain:read | List and view domains |
domain:write | Create, update, delete domains |
mailbox:read | List and view mailboxes |
mailbox:write | Create, update, delete mailboxes |
alias:read | List and view aliases |
alias:write | Create, update, delete aliases |
dkim:read | View DKIM keys |
dkim:write | Generate DKIM keys |
stats:read | View statistics and analytics |
tracking:read | View tracking events |
webhook:read | View webhook configuration |
webhook:write | Create, update, delete webhooks |
queue:read | View mail queue |
queue:write | Flush or delete queue messages |
send:email | Send emails via the API |
When permissions is NULL, the key has all permissions (backward compatible).
SMTP Authorization¶
SMTP authorization works differently. When a user authenticates via SASL:
- They can only send email from addresses they own or aliases pointing to their mailbox
- They can only access their own mailbox via IMAP/POP3
- Dovecot enforces this through its
userdbconfiguration
Sender Restrictions¶
Postfix checks that the authenticated user matches the sender address:
This prevents user@example.com from sending as admin@example.com.
Webhook Data Isolation¶
Each organization can have its own webhook endpoints. Events are only sent to:
- The organization's registered webhook URLs
- Global webhook URLs (if any)
An event from Organization A is never sent to Organization B's webhook endpoint.
Best Practices¶
- Use org-scoped keys for customer-facing integrations
- Use read-only keys for dashboards and reporting
- Set permissions explicitly instead of relying on defaults
- Audit API key usage — check the
usage_countandlast_usedfields - Delete unused keys — stale keys are a security risk
- Never share global keys with external parties