Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/DataTalksClub/datamailer/llms.txt

Use this file to discover all available pages before exploring further.

Datamailer keeps a strict separation between global contact identity and per-audience subscription state. A single email address is represented by exactly one Contact row, but that contact can carry independent subscription preferences for every audience and client that uses Datamailer. This means DataTalksClub and AI Shipping Labs can share the same underlying contact database while keeping their mailing lists and engagement history completely separate.

Entity Hierarchy

Organization
├── Audience (brand-level list)
│   ├── Tag (audience-scoped label)
│   └── Campaign
│       ├── CampaignRecipient  (one row per intended recipient)
│       │   └── EmailEvent     (append-only timeline)
│       └── EmailEvent
└── Client (application or product)
    ├── Subscription           (contact × audience × client)
    ├── EmailTemplate
    └── TransactionalMessage
            └── EmailEvent

Contact (global identity)
├── Subscription  (→ Audience + Client)
├── ContactTag    (→ Tag)
├── CampaignRecipient
└── TransactionalMessage

Core Entities

Identity and Tenancy

EntityKey FieldsPurpose
Organizationslug, nameTop-level owner; groups audiences and clients (e.g., datatalksclub)
Audienceorganization, slugBrand-level mailing list (e.g., datatalks-club)
Clientorganization, slug, is_activeApplication or product sending email (e.g., dtc-newsletter)
ClientApiKeyclient, key_hash, public_id, revoked_atNamed Bearer credentials; only the hash is stored

Contacts and Subscriptions

EntityKey FieldsPurpose
Contactemail, normalized_email, verified_at, global_unsubscribed_at, hard_bounced_at, complained_atGlobal email identity; unique on normalized_email
Subscriptioncontact, audience, client, status, verified_at, unsubscribed_atPer-audience/client opt-in state; unique on (contact, audience, client)
Tagaudience, name, slugAudience-scoped label; unique on (audience, slug)
ContactTagcontact, tagMany-to-many membership; unique on (contact, tag)

Campaign Entities

EntityKey FieldsPurpose
Campaignclient, audience, subject, html_body, status, include_tags, exclude_tagsCampaign definition plus aggregate engagement counters
CampaignRecipientcampaign, contact, status, skip_reason, tracking_token_hash, unsubscribe_token_hashOne row per intended recipient; unique on (campaign, contact)
EmailEventevent_type, campaign_recipient, transactional_message, contact, created_atAppend-only event timeline shared by campaigns and transactional sends

Transactional Entities

EntityKey FieldsPurpose
EmailTemplateclient, key, subject, html_body, text_body, is_transactionalReusable template; unique on (client, key)
TransactionalMessageclient, contact, template_key, idempotency_key, statusOne row per transactional send request; unique on (client, idempotency_key) when key is present

Key Design Decisions

Global Identity, Scoped Subscriptions

One Contact row per email address across the entire system. Subscription state is scoped to (contact, audience, client), so the same person can be subscribed to the newsletter and unsubscribed from courses independently.

Append-Only Event Log

EmailEvent rows are never updated or deleted. Every open, click, bounce, complaint, and unsubscribe is a new row. This makes the contact timeline auditable and supports future partitioning or S3 archival as volume grows.

Recipient Snapshots

When a campaign is sent, one CampaignRecipient row is created for every intended contact — including those who will be skipped. A 120k-recipient campaign produces 120k rows, giving full auditability for every send/no-send decision.

Key Constraints

ConstraintTableRule
Unique normalized_emailcontactsNo two contacts share the same casefolded email
Unique (contact, audience, client)subscriptionsOne subscription record per scope
Unique (audience, slug)tagsTag slugs are unique within an audience
Unique tracking_token_hashcampaign_recipientsEach open-pixel token maps to exactly one recipient
Unique unsubscribe_token_hashcampaign_recipientsEach unsubscribe token maps to exactly one recipient
Unique (client, idempotency_key)transactional_messagesIdempotency enforced at the database layer (when key is non-empty)
Unique provider_event_idemail_eventsSES webhook events are deduplicated by provider event ID

Build docs developers (and LLMs) love