Skip to content

Data Storage

Serverless Inbox uses a single S3 bucket and six DynamoDB tables. All tables use on-demand billing and per-account key prefixes to isolate tenant data.


The system registry. Stores all configuration that the platform needs to operate: tenant records, domain bindings, email identity definitions, authentication policies, OAuth client metadata, and sender suppression list entries.

Every Lambda that needs to resolve “who is this tenant” or “what policies apply here” reads from the Config table. It is read far more often than it is written.

Written by: bootstrap, admin-api, sqs-ses-event-processor
Read by: All Lambdas


The primary mailstore. Holds every user-owned object in the system:

  • Mailboxes — Inbox, Sent, Drafts, Trash, and any custom folders
  • Emails — Message metadata, flags, and mailbox placement
  • Threads — Conversation groupings
  • Email Identities — Sender from-address definitions per user
  • WebSocket connections — Active push registrations (written on connect, deleted on disconnect)

The Item table is the busiest table in the system and is designed around the access patterns of the JMAP protocol — most queries target a single account and a single object type.

Written by: email-processor, email-sender, sqs-ses-event-processor, jmap-api, admin-api, websocket-connect, websocket-disconnect
Read by: jmap-api, admin-api, websocket-broadcast, tantivy-indexer


Tracks the state version for every account and object type, enabling JMAP /changes queries and incremental search indexing.

Each entry records a create, update, or delete event for a single object, keyed by {accountId}#{objectType}. A separate “state counter” item tracks the current maximum sequence number. Entries carry a TTL and are automatically removed after they expire.

Written by: email-processor, email-sender, sqs-ses-event-processor, jmap-api, admin-api
Read by: jmap-api (for /changes endpoints), tantivy-indexer (to detect what changed)


A chronological record of administrative and user actions — who did what, to which resource, and when. Written asynchronously via the audit events queue so that audit persistence never blocks request handling.

Entries can be queried by entity (e.g. “all actions on tenant X”) or by actor (e.g. “all actions by admin user Y”) using the table’s secondary indexes.

Written by: audit-writer (consuming from the audit events queue)
Read by: admin-api (audit log queries)


Records delivery feedback received from the email provider for every outbound message: hard bounces, soft bounces, spam complaints, rejections, and delivery confirmations. Used to drive automatic suppression decisions and to give administrators visibility into deliverability per sender and domain.

See Bounce & Complaint Handling for how this data drives suppression.

Written by: sqs-ses-event-processor
Read by: admin-api (deliverability reporting), sqs-ses-event-processor (suppression threshold checks)


An internal cache for the OIDC public keys (JWKS) published by the configured identity provider. Populated on a schedule by the jwk-cache-updater Lambda. The authorizer and websocket-connect Lambdas read from this table to verify JWTs locally, avoiding an outbound call to the IdP on every API request.

Written by: jwk-cache-updater
Read by: authorizer, websocket-connect


A single encrypted S3 bucket serves multiple purposes, separated by key prefix. The same bucket is used by the email processing pipeline, the email sending pipeline, and the search infrastructure.

Temporary landing zone for raw emails deposited by SES during inbound receipt. The email-processor Lambda reads from this prefix, processes the message, and stores the structured content elsewhere. Objects in this prefix are automatically deleted after 7 days as a safety net for unprocessed messages.


Long-lived storage for structured email content — MIME parts, attachments, and inline blobs — organised by tenant and account. The JMAP API and the CDN blob endpoint read from here when serving message bodies and attachments to clients.


Per-account Tantivy full-text search index files, written and read exclusively by the tantivy-indexer Lambda. Each account’s index is stored as a set of segment files that the indexer loads, updates, and persists back to S3 on each indexing run.


Temporary staging area for user-uploaded attachment blobs before they are linked to an outbound email. Objects here are automatically deleted after 1 day.