concepts
Inboxes
Temporary inbox lifecycle, TTL management, and Redis routing.
Creating an Inbox
POST /api/v1/inboxes{ "domain_assignment_id": "...", "alias": "my-test", "ttl": "1h"}- alias — Optional. If omitted, a random alias is generated
- ttl — Duration string (e.g.,
10m,1h,24h). Minimum 1 minute, capped bydefaults.max_inbox_ttl
Limits: an alias is 2–64 characters, and each user may hold at most 50 active inboxes at once.
TTL Lifecycle
- Created — Inbox is active,
expires_atis set tonow + ttl - Active — Receives emails via SMTP, visible in the UI
- Extended —
POST /api/v1/inboxes/{inboxId}/extendsetsexpires_attonow + duration(it does not add to the time remaining, so extending by less than the remaining time shortens the inbox), bounded bycreated_at + max_inbox_ttl. Omitdurationand the org'srenewal_policydecides:original(the default — reuse the TTL the inbox was created with),default(re-resolve the cascade), orfixed(use the org'srenewal_ttl) - Expired — After
expires_at, the inbox stops receiving emails - Cleaned up — The cleanup worker emits
inbox.expired(webhook, audit entry, and notification), then deletes the inbox, its emails, and their stored attachments
Redis Routing
For fast SMTP routing, active inboxes are cached in Redis:
- Key:
inbox:{full_address}→ Value:{inbox_id} - TTL matches the inbox expiry
- The reconciler worker keeps Redis in sync with PostgreSQL
When an email arrives, the SMTP router checks Redis first (O(1) lookup), falling back to PostgreSQL if not found.
Private Inboxes
All inboxes are private — only the user who created the inbox can view it, extend it, delete it, or read its emails. This is enforced in the service layer, which compares inbox.created_by against the authenticated user on every access (internal/service/inbox_service.go); the WebSocket handler applies the same check before upgrading the connection. There is no privacy flag on the table — ownership is the only rule.
Creation Constraints
Creation is refused when:
- The domain is not assigned to the team, or the assignment is
read_only - The domain's MX record is not verified — mail could not be delivered
- The domain is at its active-inbox quota (the org's
max_inboxes_per_domain, else the system default), or at the owning team's own cap when the team sets one - The user already holds 50 active inboxes
- The alias is already taken on that domain (
409)
Listing
GET /api/v1/inboxes?status=active&search=alpha&page=1&per_page=20GET /api/v1/orgs/{orgId}/teams/{teamId}/inboxesstatus accepts active, expired or all; search is a case-insensitive
substring match on the full address.