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.
DynamoDB Tables
Section titled “DynamoDB Tables”Config table
Section titled “Config table”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
Item table
Section titled “Item table”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
Changelog table
Section titled “Changelog table”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)
Audit log table
Section titled “Audit log table”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)
Mail feedback table
Section titled “Mail feedback table”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)
JWK cache table
Section titled “JWK cache table”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
S3 Bucket
Section titled “S3 Bucket”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.
incoming-emails/
Section titled “incoming-emails/”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.
Per-account email storage
Section titled “Per-account email storage”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.
indexes/
Section titled “indexes/”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.
blobs/
Section titled “blobs/”Temporary staging area for user-uploaded attachment blobs before they are linked to an outbound email. Objects here are automatically deleted after 1 day.