Learn the database schema behind email drip campaigns—tables, workflow states, and event logs that power reliable marketing automation.

Marketers ask me what a drip campaign "actually is" almost every week, and there's always a second question hiding behind it: what does the database look like underneath the pretty workflow builder? If you've ever tried to debug why a contact got the wrong email, or you're scoping a custom integration with your CRM, you already know that the visual canvas is just a skin. The real answer lives in tables, foreign keys, and a queue that gets polled every few seconds.
Quick answer: An email marketing automation workflow drip campaign data structure relies on a relational schema made up of at least seven interlocking tables: campaigns, templates, workflows, workflow steps, contacts, a contact queue, and an event log. Together these tables separate three concerns: what should happen (definition), what is currently happening for each contact (state), and what already happened (history). A workflow engine reads this schema on a loop, resolves delays into timestamps, and moves each contact through the sequence one queued row at a time.
That one paragraph is the whole concept in miniature. Everything below unpacks it for two audiences at once: marketers who need to understand what their email service provider is doing behind the scenes, and developers or marketing-ops hybrids who need to design, audit, or debug the actual drip campaign database schema.
At its core, this data structure is the set of database tables and relationships that let software send the right email, to the right contact, at the right moment, without a human clicking "send" each time. It's the difference between a spreadsheet of email addresses and a system that knows Contact #4821 is on step 3 of the "trial signup" workflow, is due for the next email in six hours, and already opened the last two messages.
Every mature implementation, whether it's a homegrown Postgres schema or the internal architecture of an ESP, answers the same three questions with its tables:
Keeping these three concerns in separate tables is the single most important design decision in the whole schema, and it's the mental model worth memorizing before you look at a single field name.
If you mix these concerns into one giant table, you get exactly the kind of bugs that make drip campaigns misbehave: editing a live workflow accidentally rewrites history, or a state change gets lost because it was crammed into a definition row that nobody expected to update.
A marketer running campaigns inside an ESP dashboard will never write SQL, but the moment a sequence sends step 2 before step 1, or resends an email a contact already received, understanding this structure is what separates "file a support ticket and wait" from "tell support exactly which table to check." For developers, this schema is the actual product. Get it wrong and you'll fight data integrity bugs for years. Get it right and adding a new trigger type or channel is a schema migration, not a rebuild.
If you're building your first automated sequence rather than architecting a database, our companion guide on building an email automation workflow walks through the practical setup steps that sit on top of everything described here.
A badly modeled schema doesn't just create engineering headaches. It quietly destroys the revenue automation is supposed to generate. Segmented, automated campaigns depend entirely on accurate contact state and clean event history. If either is broken, personalization breaks with it.
Segmented email campaigns generate significantly more revenue than one-size-fits-all blasts, and personalization has moved from "nice to have" to expected. Consider these numbers:
| Statistic | Source |
|---|---|
| Marketers report email remains one of the highest-ROI channels available, frequently cited around $36-$40 for every $1 spent | Litmus, State of Email |
| Companies that excel at personalization generate more revenue from those efforts than average performers | McKinsey & Company, personalization research |
| Behavior-triggered automated emails consistently outperform generic time-based blasts on engagement metrics across ESP benchmark reports | Industry ESP benchmark reporting |
None of that ROI reaches you without a schema that can hold segmentation logic, behavioral triggers, and accurate send history. The benefits of marketing automation compound only when the underlying data model is sound.
I've seen the failure modes, and they follow a predictable pattern:
{{first_name}} strings.Every one of these is a data structure problem wearing a marketing costume.
The drip campaign data structure template includes seven core tables almost every email automation system needs, listed in the order data flows through them:
This is the top-level container. It holds the campaign's name, its owner, its status (draft, active, paused, archived), its overall goal, and timestamps for creation and last edit. Think of it as the folder everything else lives inside.
Templates store the actual email content: subject line, HTML body, plain-text fallback, and a list of the template variables (merge fields) it expects, like first_name or trial_end_date. Keeping templates in their own table, separate from workflow steps, lets one template get reused across multiple campaigns without duplication.
A workflow is the logical sequence attached to a campaign. It stores the entry trigger type (time-based, behavioral, or a hybrid), re-entry rules (can a contact go through this workflow twice?), and its overall version number. This table is pure definition.
Each row here is one email or action inside the workflow: its position in the sequence, which template it uses, its delay configuration (a number plus a unit, like "3 days" or "6 hours"), and any conditional branching rules ("if opened previous email, go to step 4a; if not, go to step 4b"). This is where drip logic and automation logic start to diverge.
This is the person record: email address, name, custom fields, lifecycle stage, consent and unsubscribe status, and increasingly, enrichment and intent-scoring fields. It's the table every other table eventually points back to via a foreign key.
This is the state table. Each row represents one contact's current position inside one active workflow: which step they're on, the resolved execute_at timestamp for their next send, and a status flag (pending, processing, sent, failed, skipped). This table changes constantly and should never be confused with the static workflow definition.
This is the history table. Every send, delivery confirmation, open, click, bounce, complaint, and unsubscribe gets an immutable, timestamped row here, referencing the contact and the workflow step that triggered it. Analytics, reporting, and compliance audits all read from this table.
| Table | Type | Key Fields | Purpose |
|---|---|---|---|
| Campaigns | Definition | id, name, status, owner_id, created_at | Top-level container for a marketing initiative |
| Templates | Definition | id, subject, html_body, variables_json | Reusable email content and merge-field contract |
| Workflows | Definition | id, campaign_id, trigger_type, reentry_allowed, version | The logical sequence and its entry rules |
| Workflow_Steps | Definition | id, workflow_id, position, template_id, delay_value, delay_unit, branch_condition | Individual emails/actions and their timing/branching |
| Contacts | Definition/Reference | id, email, custom_fields_json, consent_status, lifecycle_stage, intent_score | The person record every other table references |
| Contact_Queue | State | id, contact_id, workflow_id, current_step_id, execute_at, status | Where each contact sits right now, and when they're due next |
| Event_Logs | History | id, contact_id, workflow_step_id, event_type, occurred_at | Immutable record of every send and engagement event |
Mental model callout: If you only remember one thing from this section, remember this: Campaigns, Templates, Workflows, and Workflow_Steps define the plan. Contact_Queue tracks the plan's execution for a specific person. Event_Logs proves what actually happened. Confusing any two of these is where most schema bugs are born.
The tables above are static furniture until something reads them on a schedule. That "something" is the workflow engine, and this is the part almost no competing guide explains in real mechanical detail.
A Workflow_Step doesn't store "send in 3 days" as a vague instruction. It stores a delay_value of 3 and a delay_unit of "days." The moment a contact enters that step, the engine calculates a concrete execute_at timestamp by adding that delay to the current time (often normalized to an ISO 8601 UTC timestamp to avoid timezone bugs) and writes it into the Contact_Queue row. From that point forward, the engine only cares about whether execute_at is now in the past.
A background process, often a cron job or a persistent queue worker, runs on a tight interval (commonly every 30 to 60 seconds in high-volume systems) and asks the database one question: "Which Contact_Queue rows have execute_at in the past and a status of pending?" For every row that matches, the worker:
execute_at, or marks the workflow completeThis poll-lock-process-log cycle is the entire "engine." There's no magic, just disciplined, repeated database reads and writes.
Sends fail. APIs time out. A worker process can crash mid-send. This is where idempotency, a concept borrowed from distributed systems engineering, becomes essential: each send attempt should carry a unique idempotency key so that if the same job gets picked up twice (say, after a timeout that wasn't actually a failure), the system recognizes the duplicate and skips it rather than emailing the contact twice. Well-designed queues also track a retry_count and a last_error field, backing off exponentially between attempts and eventually marking a row as permanently failed rather than retrying forever.
Here's a simplified flow of the whole pipeline in plain text:
Trigger fires - Contact_Queue row created with resolved execute_at - Queue worker polls and locks due rows - Template resolved and email sent - Event_Logs row written - Contact_Queue row advanced to next step (or marked complete)
This is the distinction most competing articles gloss over, and it's a genuinely different schema shape, not just a marketing label.
A pure drip campaign is time-based: contact enters, a fixed sequence of delays fires emails in order, done. A marketing automation workflow is behavior-based: it listens for events, evaluates conditions against contact data, and branches accordingly, sometimes looping a contact back through earlier steps or exiting them early.
| Aspect | Drip Campaign Schema | Marketing Automation Workflow Schema |
|---|---|---|
| Trigger type | Single time-based trigger (e.g., "signup date") | Multiple trigger types: time-based, behavioral (event listener), or attribute-based |
| Sequence shape | Linear, fixed order of steps | Branching, with conditional paths and possible loops |
| Workflow_Steps complexity | Delay - template, nothing else | Delay, template, plus branch_condition and next_step_if_true/false fields |
| Re-entry rules | Usually disallowed (one pass per contact) | Often configurable (a contact can re-enter after a cooldown or new event) |
| Event listening | Not required; the engine only checks the clock | Requires an event listener or webhook ingestion layer feeding Event_Logs in near real time |
| Data needed on Contacts | Minimal: email, name, signup_date | Extensive: behavioral flags, lifecycle_stage, custom_fields, intent_score |
| Typical complexity to build | Low; a junior developer can ship this in days | Higher; requires an event-driven architecture or webhook processing pipeline |
| Best fit | Onboarding sequences, newsletter welcome series | Lead nurturing, re-engagement, cross-channel journeys |
The practical takeaway: if your Workflow_Steps table has no branch_condition field and your Contacts table has no behavioral event feed, you have a drip schema, even if the vendor calls it "automation." True behavioral automation requires an event-driven layer sitting on top of the same base tables.
For a deeper look at where drip fits inside the broader discipline, our guide on what a marketing automation workflow actually is breaks down the strategic side of this same technical distinction.
Theory is fine, but nothing beats watching real rows change. Let's trace a single contact, Maria, through a SaaS trial-signup drip campaign, table by table.
Maria signs up for a free trial. The application inserts a new row into Contacts and fires a "trial_signup" event.
{
"id": 4821,
"email": "maria@example.com",
"custom_fields": { "first_name": "Maria", "trial_plan": "Pro" },
"consent_status": "confirmed",
"lifecycle_stage": "trial",
"intent_score": null,
"created_at": "2026-09-20T14:02:11Z"
}The "trial_signup" event matches the trigger on the "Trial Onboarding" workflow (workflow_id: 12). The engine creates a Contact_Queue row, resolving the first step's delay (0 minutes, sent immediately) into an execute_at timestamp.
INSERT INTO contact_queue (contact_id, workflow_id, current_step_id, execute_at, status)
VALUES (4821, 12, 101, '2026-09-20T14:02:11Z', 'pending');The queue worker picks up the row, resolves the template's merge fields against Maria's Contacts row, sends the welcome email, and writes an Event_Logs entry. It then advances Maria to step 2, a "3-day check-in," calculating the new execute_at as September 23 at the same time.
UPDATE contact_queue
SET current_step_id = 102,
execute_at = '2026-09-23T14:02:11Z',
status = 'pending'
WHERE contact_id = 4821 AND workflow_id = 12;Maria opens the welcome email four minutes later. A tracking pixel fires a webhook, and the system writes another Event_Logs row.
{
"id": 88291,
"contact_id": 4821,
"workflow_step_id": 101,
"event_type": "email_opened",
"occurred_at": "2026-09-20T14:06:55Z"
}If Maria's plan supports behavioral branching, that open event could also update her intent_score and change which of two possible step 2 templates she receives three days later. That's the exact mechanical difference between a drip schema and an automation schema, made concrete.
If you want to see finished sequences before building your own, our roundup of email drip campaign templates shows what these worked examples look like once they're turned into real copy and timing.
Whether you're building from scratch or auditing an existing ESP integration, the same checklist applies.
7-Point Schema Audit Checklist
This is the section I wish existed the first time a client asked me why their re-engagement campaign emailed someone who had already unsubscribed three weeks earlier.
When a send looks wrong, work through the schema in this order:
| Symptom | Most Likely Table | What to Check |
|---|---|---|
| Contact received duplicate emails | Contact_Queue / send layer | Missing idempotency key or retry without deduplication |
| Contact stuck on the same step for days | Contact_Queue | execute_at not recalculated after send, or worker crash mid-transaction |
| Wrong template content sent | Workflow_Steps | Live edit changed step order or template_id without versioning |
| Unsubscribed contact still received email | Contacts | consent_status not checked before queue insert |
| Blank merge fields in the email | Templates / Contacts | Template references a custom field the contact record doesn't have |
| No record of what happened | Event_Logs | Logging not wired into the send function, or logs were mutated instead of appended |
Solid execution logic here depends heavily on your sending infrastructure staying healthy. Our email deliverability best practices guide covers the transport-layer half of this troubleshooting picture.
Here's a video that walks through the basic mechanics of a drip campaign for readers who want a visual refresher before going deeper into the AI-era schema below.
The seven-table schema above has been stable for roughly two decades. What's changing in 2026 isn't the core shape, it's what gets attached to the Contacts table and how segmentation logic gets evaluated.
A standard Contacts table is a flat list of rows. An audience graph extends that into a network: relationships between contacts (colleague, referral source, same company domain), weighted by behavioral signals, so segmentation stops being "match this static field" and starts being "find contacts similar to my best customers." Intent scoring adds a continuously recalculated numeric field, often updated by a background job that reads recent Event_Logs activity, that feeds directly into branch_condition logic on Workflow_Steps. A high intent score can route a contact down a faster, more direct path; a cooling score can trigger a re-engagement branch instead.
Enrichment services append firmographic and demographic data (company size, industry, job title, technographic signals) onto the Contacts record, typically through an asynchronous API call after signup rather than at the moment of insert. This means the Contacts table increasingly needs nullable, JSON-friendly enrichment fields that get filled in over time, plus a timestamp recording when enrichment last ran. Our breakdown of data enrichment services for B2B marketers covers how these providers actually populate that data, and it's worth reading if you're modeling this layer yourself.
Consent and privacy modeling deserves the same rigor as enrichment. A consent_status field alone isn't enough; mature schemas store the specific legal basis for processing, a timestamp of when consent was captured, and the source of that consent, aligned with the requirements described in the official EU GDPR text. If your list-building practices predate this discipline, our piece on scraped email lists and GDPR explains exactly why skipping this modeling work tends to backfire.
The newest layer sitting on top of this whole structure is a standardized API surface that lets an AI system read and write against the schema without a human writing SQL by hand. The Model Context Protocol (MCP), an open standard for connecting AI assistants to external data sources and tools, is a notable example of this pattern showing up across the software industry, letting an AI agent query a contact's current queue state or intent score and make a segmentation decision in natural language rather than a hardcoded rule.
A practitioner's note on how Breaker extends this schema: Breaker's audience graph and intent-scoring layer sit directly on top of the same core tables described in this article - contacts, workflows, and an event log - but expose them through an API/MCP layer so segmentation logic can be expressed as an intent, not a SQL query. That's a vendor-specific implementation of the general pattern described above, not a claim that every ESP works this way. If you're evaluating whether your current platform's data structure can actually support this kind of AI-native segmentation, our Breaker platform review lays out the specifics.
Not everyone needs to design a schema from scratch, and that's a legitimate choice, not a shortcut.
| Approach | Best For | Trade-offs |
|---|---|---|
| DIY relational schema (Postgres/MySQL) | Engineering teams building a custom product, or unusual compliance/data-residency requirements | Full control, but you own every bug, migration, and scaling problem |
| No-code ESP (standard workflow builder) | Marketing teams without dedicated engineering support who need reliable time-based and basic behavioral drips | Fast to launch, but segmentation logic is limited to what the vendor's UI exposes |
| AI-native ESP with audience graph and API/MCP layer | Teams that want behavioral and intent-based segmentation without writing SQL, and want that logic to evolve automatically as new signals arrive | Newer category, so evaluate the vendor's actual data model, not just marketing language |
If your team is mostly marketers who need dependable sends and clear reporting without managing infrastructure, Breaker is built specifically for that middle ground: the audience graph and event log described in this article power the platform directly, so you get the reliability of a properly normalized schema with the simplicity of a no-code interface. It's worth comparing directly against a traditional workflow builder like HubSpot or a legacy ESP like Mailchimp on exactly this point: can the platform's underlying data structure support true behavioral branching and intent scoring, or is it a time-based drip schema wearing an "automation" label? For B2B teams specifically, our B2B newsletter strategy guide and our B2B content engine guide both build on this same underlying data model in practice.
You can see the full breakdown of Breaker's approach, pricing, and feature set on the Breaker homepage.
What is a drip campaign data structure? It's the set of database tables - typically covering campaigns, templates, workflow steps, contacts, a processing queue, and an event log - that together define, track, and record an automated sequence of timed emails sent to a contact.
What database tables are required to build an email automation workflow? At minimum: a Campaigns table, a Templates table, a Workflows table, a Workflow_Steps table, a Contacts table, a Contact_Queue table for state, and an Event_Logs table for history. Larger systems add supporting tables for segments, tags, and enrichment data.
What's the difference between a drip campaign and a marketing automation workflow at the data level? A drip campaign schema is linear and time-based, with Workflow_Steps containing only a delay and a template. A marketing automation workflow schema adds branch_condition fields, event listeners, and re-entry rules, letting the sequence change direction based on contact behavior rather than the calendar alone.
How does an email workflow engine decide when to send the next email in a sequence? The engine resolves each step's configured delay into a concrete execute_at timestamp the moment a contact enters that step, then a background queue worker polls the database on a fixed interval, sending any queued row whose execute_at timestamp has already passed.
What is a contact queue in email automation? A contact queue is the state table that tracks exactly where each individual contact sits inside an active workflow: their current step, their next scheduled send time, and a status flag like pending, sent, or failed.
What is an event log used for in drip campaigns? An event log is an append-only history table recording every send, delivery, open, click, bounce, and unsubscribe, which powers analytics dashboards, deliverability troubleshooting, and compliance audits.
Can a drip campaign schema support behavioral triggers? Not without modification. A pure drip schema only checks timestamps. Supporting behavioral triggers requires adding an event listener layer that writes to the Event_Logs table in near real time and adding branch_condition logic to the Workflow_Steps table.
How do you debug a drip campaign that sent the wrong email? Check the Contact_Queue row first to see the contact's current step and timestamp, then check the Workflow_Steps definition for recent edits, then check the Event_Logs table for duplicate or missing events, and finally check the Contacts row for stale consent or custom field data.
What fields should a Contacts table include for personalization? At minimum, an email address, name fields, a flexible custom_fields column (often JSON), a lifecycle_stage field, a consent_status field with a timestamp, and increasingly an intent_score and enrichment fields for firmographic data.
Is a drip campaign always time-based? Traditionally yes. A classic drip campaign fires purely on elapsed time since a trigger event. Many modern ESPs blur the line by adding light conditional logic on top of an otherwise time-based sequence, which is technically a hybrid schema rather than a pure drip.
How does an audience graph differ from a standard contacts table? A standard contacts table is a flat list of individual records. An audience graph models relationships and behavioral similarity between contacts, enabling segmentation based on patterns across the whole audience rather than isolated field values on one record.
What database type is best for email automation, SQL or NoSQL? Relational (SQL) databases suit most email automation systems well because the tables have clear foreign key relationships (a contact belongs to a queue row belongs to a workflow). NoSQL document stores can work for very high-volume event logging or flexible custom-field storage, and many production systems use both: SQL for the core schema, a document store or time-series database for event logs at scale.
How do enrichment and intent scoring fit into the data structure? Enrichment services populate additional fields on the Contacts table asynchronously after signup, typically through an API call, while intent scoring is a continuously recalculated numeric field derived from recent Event_Logs activity, both of which then feed into branch_condition logic for smarter segmentation.
Do I need to understand the data structure if I use a no-code ESP? Not to build a basic sequence, but understanding the underlying tables helps you debug send issues, evaluate whether a vendor's "automation" claims match true behavioral capability, and communicate requirements clearly if you ever need custom integration work.
The schema described across this guide isn't going away, but the fields attached to it keep expanding as intent scoring, enrichment, and API-driven segmentation become standard rather than exceptional. Whether you're auditing an existing ESP or scoping a new build, the definition-state-history mental model from the top of this guide will still be the right lens to use.
An unbiased Breaker review: pricing, features, deliverability data, and who this AI-native email platform fits best for newsletter growth.
Learn how B2B, SME, and consumer audiences differ in newsletters, plus a framework for segmenting, messaging, and monetizing each without losing relevance.
Learn what a B2B content engine is, how it differs from a content calendar or strategy, and why it's essential for AI-driven search in 2026.