Email Studio Database
Email Studio owns its own schema. The tables are designed for auditability first: the package stores the template, rendered content, recipients, provider result, and suppression state needed to explain a send later.
Tables
Section titled “Tables”| Table | Purpose |
|---|---|
email_profiles | Sender identity, reply-to settings, provider type, tracking defaults, and provider settings. |
email_templates | Template identity, status, declared variables, name, and description. |
email_template_variants | Sendable template content by site scope, locale, profile, status, and version. |
email_messages | One send request, including rendered subject/body snapshots and trigger context. |
email_recipients | Per-recipient lifecycle state, provider message IDs, failure reasons, and event timestamps. |
email_events | Provider lifecycle events such as delivered, bounced, complained, opened, clicked, replied, and failed. |
email_replies | Inbound reply records linked to messages and recipients. |
email_suppressions | Active or released suppressions by normalized email hash and site scope. |
email_template_registrations | Package-owned template definitions and variable contracts. |
email_tracking_tokens | Opaque public tokens for open and click tracking. |
Scope Columns
Section titled “Scope Columns”Privacy-sensitive and site-owned records store:
site_id, nullable for global records;site_scope_key, always set to a value such asglobalorsite:12.
Use site_scope_key for uniqueness and lookup logic. Do not rely on nullable site_id to distinguish global records.
Template Versioning
Section titled “Template Versioning”email_templates stores the stable key and declared variables. email_template_variants stores the subject, preview text, HTML body, plain text body, locale, status, and version.
Only approved templates and active variants should be used for production sends. Retired variants stay available for audit history because old messages reference the variant used at send time.
Message Snapshots
Section titled “Message Snapshots”email_messages stores rendered subject, preview text, HTML, plain text, headers, and context snapshot.
This is intentional duplication. If an editor changes a template tomorrow, yesterday’s sent message still needs to show what was rendered yesterday.
The capell-email-studio:prune-bodies command clears rendered HTML and plain-text bodies after the configured retention window while preserving delivery metadata and the context snapshot. Use its --dry-run option to review the affected messages before pruning.
Recipient State
Section titled “Recipient State”email_recipients stores one row per recipient. The current send pipeline writes:
queuedwhen the recipient can be sent;suppressedwhen an active suppression blocks delivery;sentwhen the provider accepts the send;failedwhen provider delivery fails.
The provider-event webhook can subsequently move a matching recipient into delivered, bounced, complained, opened, clicked, or replied. Events are matched by provider message ID first, then the recipient’s normalized email within the delivery profile.
Suppressions
Section titled “Suppressions”Suppressions store the original email, normalized email, and a SHA-256 hash of the normalized email.
The unique key is:
site_scope_key + email_hash + reason + sourceThat lets Email Studio keep separate provider, complaint, bounce, unsubscribe, and manual suppressions without creating duplicate active rows for the same source.
Released suppressions keep their history by setting released_at. Suppression checks ignore released rows.
Provider Events And Replies
Section titled “Provider Events And Replies”The provider-event webhook is a shipped ingestion path. It normalizes a payload into ProviderWebhookEventData, stores it idempotently per email profile and event key, updates a matched recipient, and creates a suppression for bounce and complaint events.
email_replies is ready for provider integrations to persist normalized inbound replies, but Email Studio does not currently expose an inbound-reply ingestion route or action. Do not assume a replied event payload creates an email_replies row; it records the recipient event state only.
Tracking Tokens
Section titled “Tracking Tokens”Tracking tokens must remain opaque. Public open and click routes should resolve by token only and return generic responses for invalid or expired tokens.
Do not expose message IDs, recipient IDs, template keys, package names, or admin state in public tracking URLs.
erDiagram EMAIL_PROFILES ||--o{ EMAIL_MESSAGES : sends EMAIL_TEMPLATES ||--o{ EMAIL_TEMPLATE_VARIANTS : versions EMAIL_TEMPLATES ||--o{ EMAIL_MESSAGES : used_by EMAIL_TEMPLATE_VARIANTS ||--o{ EMAIL_MESSAGES : rendered_from EMAIL_MESSAGES ||--o{ EMAIL_RECIPIENTS : contains EMAIL_MESSAGES ||--o{ EMAIL_EVENTS : records EMAIL_MESSAGES ||--o{ EMAIL_REPLIES : receives EMAIL_RECIPIENTS ||--o{ EMAIL_EVENTS : records EMAIL_RECIPIENTS ||--o{ EMAIL_REPLIES : receives EMAIL_RECIPIENTS ||--o{ EMAIL_TRACKING_TOKENS : owns
EMAIL_PROFILES { bigint id PK bigint site_id string site_scope_key string provider string from_email }
EMAIL_TEMPLATES { bigint id PK bigint site_id string site_scope_key string key string status }
EMAIL_MESSAGES { bigint id PK bigint email_profile_id FK bigint email_template_id FK bigint email_template_variant_id FK string status string subject }
EMAIL_RECIPIENTS { bigint id PK bigint email_message_id FK string email_hash string status string provider_message_id }