The email marketing automation workflow drip campaign data structure is the relational database schema and execution logic that turns a “series of emails” into an automated system. It is built from a small set of connected tables (campaigns, templates, workflows, workflow steps, contacts, a live queue, and an event log) plus a state model that decides what fires next and when. If you have ever wondered what actually happens between a trigger and a sent email, this guide maps every table, field, and rule involved, so keep reading for the full schema and the logic that runs on top of it.

Marketers usually meet drip campaigns through a visual builder. Behind that builder, though, is a database that any engineer would recognize. Understanding it helps you design flows that scale, debug sends that look wrong, and measure results from the source data rather than a dashboard summary.
What Is the Email Marketing Automation Workflow Drip Campaign Data Structure?
Quick Answer: The email marketing automation workflow drip campaign data structure is a relational schema of connected tables plus an execution engine. Tables store campaigns, templates, workflows, steps, contacts, live journey state, and event history. The engine reads that state, resolves delays into timestamps, and sends the right email at the right moment.
At a high level, the structure separates three concerns. First, definition: what the campaign is, which emails it contains, and what logic governs it. Second, state: where each individual contact currently sits inside the flow. Third, history: what has already happened to each contact, such as sends, opens, and clicks. Keeping these concerns in separate tables is what lets one workflow serve one contact or one million contacts with the same code.
This separation also explains a common source of confusion. The workflow definition is static and shared. The journey state is per contact and constantly changing. When a send looks wrong, the answer is almost always in the state table, not the definition.
Why the Data Structure Behind Drip Automation Matters
The data structure behind drip automation matters because it is what makes timing, relevance, and scale possible at the same time. A well-modeled schema lets the system react to behavior in the moment, personalize at the field level, and keep a reliable record for measurement. Those three capabilities are exactly what separate high-return email programs from batch-and-blast sends.
The financial case is well documented. Email remains one of the highest-return marketing channels, and personalization amplifies that return substantially. The table below summarizes the headline figures that a solid data structure helps you capture.
Table 1: Business value the data structure helps unlock
| Metric | Figure | Source |
| Average email marketing ROI | $36 returned per $1 spent | Litmus |
| Revenue lift from personalization | 5 to 15 percent | McKinsey |
| Marketing ROI improvement from personalization | 10 to 30 percent | McKinsey |
Sources: Litmus, Email Marketing ROI; McKinsey, What Is Personalization.
The takeaway for a marketer or a developer is practical. Personalization and behavioral timing are not features you bolt on at the end. They are properties of the schema. If your contact records store rich custom fields and your event log captures behavior, the workflow can act on both. If they do not, no amount of copywriting recovers the lost return. This is also why moving from generic broadcasts to structured, behavior-driven flows is central to any modern B2C marketing automation program.
The Core Tables in a Drip Campaign Database Schema
The core tables in a drip campaign database schema are the seven entities that store the campaign definition, the individual contacts, the live journey state, and the historical events. Each table has one clear job, and they connect through foreign keys so the system can join them at send time.
Table 2: Core tables reference
| Table | Key fields | Purpose |
| Campaigns | campaign_id, name, type | Defines the overall strategy, such as onboarding or abandoned cart. |
| Templates | template_id, subject, html_content, variables | Stores the email copy, design, and merge placeholders. |
| Workflows | workflow_id, campaign_id, trigger_type, trigger_criteria | Holds the branching logic and the rule that starts the flow. |
| Workflow_Steps | step_id, workflow_id, action_type, delay_seconds, template_id, next_step_id | Defines the ordered sequence of actions, delays, and which template each step sends. |
| Contacts | contact_id, email, status, custom_fields | Stores recipient data, lifecycle stage, and subscription state. |
| Contact_Queue | queue_id, contact_id, workflow_id, current_step_id, execute_at, status | Tracks exactly where each contact is and when the next event fires. |
| Event_Logs | log_id, contact_id, template_id, event_type, timestamp | Records historical engagement such as sent, opened, and clicked. |
The sections below explain how each group of tables behaves in practice.
Campaigns and Templates Tables
The Campaigns and Templates tables store the reusable definition of what you send. The Campaigns table names the overall strategy and its type, and it acts as the parent that groups related workflows. The Templates table stores each email as subject, body, and a set of variables, which are the merge placeholders like first name or product name that get filled in per contact at send time.
Keeping templates separate from workflows is deliberate. One template can be reused across several steps or several campaigns. When you edit the copy in one place, every step that references that template_id sends the updated version. This is also where personalization lives structurally, because the variables in a template map directly to fields on the contact record.
Workflows and Workflow Steps Tables
The Workflows and Workflow_Steps tables store the logic and the ordered sequence of a drip flow. The Workflows table records the trigger that starts the flow and the criteria that must be met, such as “joined list” or “cart abandoned.” The Workflow_Steps table then holds each action in order, including which template to send, how long to wait, and which step comes next.
The most important field here is often the delay. A wait between emails is not a vague pause. It is stored as a concrete number, usually delay_seconds, on the step. A one-day gap is stored as 86400 seconds. Because the delay is data, the engine can calculate an exact fire time rather than relying on a running timer. This design is what keeps long sequences reliable even if the system restarts. Well-planned sequences like these are the backbone of effective email nurture campaigns.
The Contacts Table and Custom Fields
The Contacts table stores each recipient along with the custom fields that drive targeting and personalization. Beyond the email address, it holds the subscription status, the lifecycle stage, and a flexible set of custom fields such as location, plan tier, last purchase date, or any attribute you collect. These fields are the raw material for both segmentation and merge-field personalization.
Custom fields are frequently modeled in one of two ways. Some schemas add columns directly to the contact record for common attributes. Others store variable attributes in a separate key-value table or a JSON column, which allows new fields without changing the table structure. The right choice depends on how many fields you expect and how often they change. Rich contact data is also what makes advanced email list segmentation possible, because a segment is really just a query across these fields and the event history.
The Contact Queue: Tracking Journey State
The Contact_Queue is the table that tracks exactly where each contact sits inside a workflow and when their next event should fire. It is the single most important table for understanding live behavior. Each row links a contact to a workflow, records the current step, stores an execute_at timestamp for the next action, and holds a status such as active, waiting, completed, or exited.
This is the “active journey state” of the whole system. If you want to know why a contact received an email or when they will receive the next one, you read this row. When a marketer says a subscriber is “stuck” in a flow, what they usually mean is that a queue row has an execute_at in the future or a status that is not advancing. The queue is also what makes the system efficient at scale, because the engine only needs to look at rows whose execute_at has arrived rather than scanning every contact.
The Event Logs Table
The Event_Logs table stores the historical record of everything that has happened to each contact. Every send, delivery, open, click, bounce, and unsubscribe becomes a row with a contact_id, the template involved, an event_type, and a timestamp. This log is append-only, meaning rows are added but not changed, which keeps an accurate audit trail.
The event log serves three jobs at once. It powers reporting and revenue attribution. It feeds decision steps, so a workflow can branch based on whether a contact opened the previous email. And it supports compliance, because it proves when a contact was sent a message and when they opted out. Much of this same event model underlies transactional email, where per-message delivery tracking is essential.
Field-Level Reference: Data Types for a Drip Campaign Schema
The data types for a drip campaign schema follow standard relational conventions: identifiers as integers or UUIDs, text for content, integers for delays, timestamps for scheduling, and enumerations for status fields. Choosing the right type for each field prevents subtle bugs, especially around time and status.
Table 3: Common fields and recommended data types
| Field | Type | Example | Purpose |
| workflow_id, contact_id | UUID or BIGINT | 8f3a… or 10294 | Unique primary and foreign keys. |
| trigger_type | ENUM or VARCHAR | joined_list, cart_abandoned | Defines what starts the workflow. |
| action_type | ENUM | send_email, delay, branch, exit | Defines what a step does. |
| delay_seconds | INTEGER | 86400 | Wait time before the next action. |
| execute_at | TIMESTAMP | 2026-02-01T09:00:00Z | When the next action should fire. |
| status | ENUM | active, waiting, completed, exited | Current state of a queue row. |
| event_type | ENUM | sent, opened, clicked, bounced | Type of logged engagement event. |
| custom_fields | JSONB or key-value | {“plan”:”pro”} | Flexible per-contact attributes. |
A short interpretation for implementers: store all times as timestamps in a single timezone, usually UTC, and convert only for display. Store delays as integers of seconds rather than human-readable strings. Use enumerations for status and event fields so the values stay consistent and queryable. These small choices are what keep the queue reliable and the reporting accurate.
How the Workflow Engine Executes Each Step
The workflow engine executes each step in a repeating four-stage cycle: trigger evaluation, step resolution, execution, and transition. This cycle runs continuously in the background and is the heart of the automation. Here is the sequence in order.
- Trigger evaluation. When an event occurs, such as a contact joining a list or abandoning a cart, the system checks the workflow trigger criteria. If the contact qualifies, the engine creates a new row in the Contact_Queue that points to the first step.
- Step resolution. The engine reads the current step and calculates the execute_at timestamp by adding the step delay_seconds to the trigger time. A step with an 86400 second delay resolves to a fire time exactly one day later.
- Execution. A scheduler or message broker polls the Contact_Queue for rows where execute_at is now or in the past. For each due row, the engine renders the referenced template with the contact custom fields, sends the email, and writes a row to Event_Logs.
- Transition. The queue row updates to point to the next_step_id. If the next step is a delay, a new execute_at is calculated. If the steps are exhausted or a goal is met, the contact status is set to completed or exited and the row leaves the active set.
This loop is why automation feels instant to a subscriber but is actually driven by a patient, poll-based scheduler. It also explains why the queue is the place to look when timing behaves unexpectedly.
State Machines and Directed Acyclic Graphs: How Drip Logic Is Modeled
Quick Answer: Drip logic is usually modeled as a state machine, a directed acyclic graph, or a blend of both. A state machine tracks each contact as a single current state that changes on events. A directed acyclic graph represents the flow as ordered steps with dependencies and no loops back on themselves.
A state machine view fits the Contact_Queue perfectly. Each contact has one current step, and events move them from one state to the next. This is simple to reason about and easy to store, because the state is just a field on a row.
A directed acyclic graph, or DAG, describes the workflow definition itself. In a DAG, each step is a node and each dependency is an edge, and the “acyclic” property guarantees there are no infinite loops. This is the same abstraction that dedicated workflow orchestration tools use. The Apache Airflow documentation describes a workflow as a DAG of tasks arranged with dependencies that define the execution order, which is exactly how a drip flow is structured. In practice, marketing platforms often combine both ideas. The definition is a graph of steps, and each contact is a state machine walking that graph.
How Conditional Branching Is Stored in the Data Structure
Conditional branching is stored in the data structure using decision steps and multiple next-step pointers. Instead of a single next_step_id, a branch step evaluates a condition, such as “did the contact open the last email,” and points to different steps depending on the answer. The condition and its outcomes are stored as data, not code.
There are two common patterns. In the first, a step carries a branch condition plus two pointers, often named next_step_if_true and next_step_if_false. In the second, branching is stored in a separate transitions table, where each row records a from-step, a condition, and a to-step. The second pattern scales better for complex flows with many branches.
Table 4: Step action types and how they store logic
| action_type | Stores | Effect on the queue |
| send_email | template_id | Renders and sends, then advances to next step. |
| delay | delay_seconds | Sets a future execute_at, then waits. |
| branch | condition, true and false pointers | Reads event data, routes the contact down one path. |
| update_field | field, value | Writes to the contact record, then advances. |
| exit | goal or condition | Marks the queue row completed or exited. |
The interpretation for designers is that every “if this, then that” decision a marketer draws in a builder becomes a branch row with a stored condition. The visual arrow is really a pointer between step records.
How Email Events and Engagement Logs Are Structured
Quick Answer: Email events and engagement logs are structured as append-only rows, each tagging a contact, a message, an event_type, and a timestamp. Common event types include sent, delivered, opened, clicked, bounced, unsubscribed, and complained. This log feeds reporting, branching decisions, and compliance records.
Storing events as individual rows rather than counters is a deliberate choice. Counters can only tell you a total. A row-level log tells you who did what and when, which supports both segmentation and per-contact troubleshooting. It also lets you rebuild any metric later, because the raw history is intact.
Table 5: Common email event types in the log
| event_type | Meaning | Typical use |
| sent | The platform handed the message to the mail server. | Volume and pacing. |
| delivered | The receiving server accepted the message. | Deliverability tracking. |
| opened | The recipient opened the email. | Engagement and branching. |
| clicked | The recipient clicked a link. | Intent and conversion. |
| bounced | Delivery failed, hard or soft. | List hygiene and suppression. |
| unsubscribed | The recipient opted out. | Compliance and suppression. |
A short interpretation: open data has become less precise because some mail clients pre-load images, so many teams weight clicks and conversions more heavily. The log structure does not change, but how you interpret each event type should.
How Triggers and Webhooks Feed the Workflow With JSON Payloads
Quick Answer: Triggers and webhooks feed the workflow by delivering a JSON payload that identifies the contact and the event. The platform reads the payload, matches it to a workflow trigger, and creates a Contact_Queue row. This is how external systems like a store or an app start an automated sequence in real time.
An incoming webhook is simply an HTTP request that carries structured data. A typical trigger payload looks like this:
{
"event": "cart_abandoned",
"contact": {
"email": "[email protected]",
"first_name": "Jordan",
"custom_fields": { "cart_value": 148.00, "items": 3 }
},
"occurred_at": "2026-02-01T14:12:00Z"
}
When this arrives, the engine looks up any workflow whose trigger_type matches “cart_abandoned,” confirms the contact meets the trigger_criteria, and inserts a queue row at the first step. The custom_fields in the payload can update the contact record so later steps personalize on cart value or item count. This webhook-driven model is what lets a drip flow react within seconds of a real-world action rather than on a fixed schedule.
Reliable Sends: Idempotency, Queues, and Deduplication
Reliable sends depend on idempotency, which means processing the same instruction twice produces the same result as processing it once. In any queued system, a message can be delivered more than once because of retries or restarts, so the engine must be able to recognize and skip a duplicate before it sends a second email.
The standard approach is to attach a unique identifier to each send instruction and record it once processing succeeds. Before sending, the engine checks whether that identifier has already been handled, and if so, it skips the send. The idempotent consumer pattern describes this exactly: the consumer records the IDs of processed messages and discards duplicates by checking that record. In the drip schema, this often means a unique constraint on a combination like contact_id plus step_id plus a send key, so the same step cannot fire twice for the same contact.
This is not a minor detail. Duplicate sends erode trust fast, and at scale they can trigger spam complaints. The queue plus an idempotency check is what turns “usually fine” into “reliable.” The same reliability thinking applies to the underlying sending path, which is why teams that send high volumes pay close attention to their SMTP relay infrastructure.
Suppression, Consent, and Compliance Fields in the Schema
Suppression, consent, and compliance are handled in the schema through a suppression list, consent fields on the contact record, and honored opt-out events. A suppression list is a table of contacts or addresses that must be excluded from sends, and the engine checks it before every send. Consent fields record whether and when a contact agreed to receive email.
This is a legal requirement, not just good practice. In the United States, the CAN-SPAM Act requires that every commercial email include a working opt-out mechanism and that opt-out requests be honored, with per-email penalties for violations. In the European Union, the General Data Protection Regulation sets rules for consent and for storing personal data. Structurally, this means the schema should store a consent status, a consent timestamp, and an unsubscribe event, and the send logic must consult all three. A suppression check before send is the safeguard that keeps a fast automated system compliant.
Common Mistakes When Designing a Drip Campaign Data Structure
The most common mistakes when designing a drip campaign data structure come from mixing concerns, ignoring time zones, and skipping deduplication. Each one causes real, visible problems in production.
- Storing state in the workflow definition. Journey state belongs in the queue, not in the shared workflow record. Mixing them makes every contact share one position, which breaks at the first concurrent send.
- Storing delays as text or relying on live timers. Delays should be integers of seconds resolved into an execute_at timestamp. Live timers do not survive restarts.
- Using a single giant table. Cramming campaigns, contacts, and events into one table destroys performance and makes queries fragile. Separate tables joined by keys scale far better.
- Skipping idempotency. Without a uniqueness check, retries cause duplicate emails. Add a unique constraint on the send identifier.
- Logging counters instead of events. Counters cannot answer “who and when.” Store row-level events so you can rebuild any metric and branch on behavior.
- Ignoring suppression at send time. Checking suppression only at import is not enough. Check it immediately before every send.
Avoiding these keeps the system predictable, which is the whole point of automation.
A Practical Checklist for Designing a Drip Campaign Data Structure
A practical checklist for designing a drip campaign data structure walks through the tables and constraints in the order you should build them. Follow these steps to move from a blank database to a working schema.
- Define the contact record first. Create the Contacts table with a unique email, a status field, and a place for custom fields. Everything else references this record, so get the identity and consent fields right before anything else.
- Separate content from logic. Build the Templates table for reusable email content and the Workflows table for triggers and criteria. Never hard-code copy into a workflow, because you will want to reuse and edit it independently.
- Model steps as ordered rows. Create the Workflow_Steps table with an action_type, a delay_seconds integer, a template reference, and a next_step_id pointer. This turns the visual flow into queryable data.
- Add the live queue. Build the Contact_Queue table with a current_step_id, an execute_at timestamp, and a status. This is the table your scheduler will poll, so index execute_at for speed.
- Log every event. Create the append-only Event_Logs table so you can measure, branch, and prove compliance later.
- Enforce uniqueness for reliability. Add a unique constraint on the send identifier so a step cannot fire twice for the same contact.
- Wire in suppression and consent. Add a suppression list and check it, plus consent fields, before any send leaves the system.
Build in this order and each table has what it needs before the next one depends on it. The result is a schema that is easy to query, safe to scale, and simple to debug.
How Emercury Implements the Drip Campaign Data Structure
Emercury implements the drip campaign data structure through named features that map cleanly onto each layer of the schema described above. Rather than exposing raw tables, the platform gives marketers a visual layer over the same underlying model of workflows, steps, contacts, queue state, and events.
Mapping the Email Marketing Automation Workflow Drip Campaign Data Structure to Emercury Features
Mapping the email marketing automation workflow drip campaign data structure to Emercury features shows how each abstract table corresponds to a real, usable tool.
- Journey Builder is the visual automation builder that represents the Workflows and Workflow_Steps layer. You define triggers, delays, and branches without writing schema by hand.
- Event-based triggers and custom events tracking cover the trigger evaluation stage, feeding the queue when a contact takes a qualifying action.
- Smart Segments provide real-time tracking of segment entry and exit, which is the live, query-driven view of the Contacts table.
- Virtual Segments are one-time-use segments for throttling, useful when you need to control send pacing across the queue.
- Incoming Webhooks let you feed data from external platforms, which is the JSON payload path that starts or updates a workflow.
- Smart Personalization delivers conditional content based on subscriber data, which is the Templates plus custom fields layer in action.
- Message Center gives a full messaging history per contact, mirroring the Event_Logs table at the individual level.
- Suppression Lists exclude contacts from campaigns, implementing the compliance safeguard before send.
- ECPM Reporting tracks revenue per subscriber, derived from the event history.
Because Emercury keeps features available across all tiers rather than gating them, you can build the full structure without unlocking pieces one plan at a time. Support comes from an in-house team of email experts rather than chatbots, which helps when you are wiring triggers and webhooks for the first time.
Conclusion
The email marketing automation workflow drip campaign data structure is not a mystery reserved for engineers. It is a small, logical set of tables and rules: definitions in campaigns, templates, and workflows; live state in the contact queue; and history in the event log, all driven by a simple execution cycle of trigger, resolve, send, and transition. Once you can see that structure, you can design flows that scale, debug sends that look wrong, and measure results straight from the source data. If you want a platform that gives you this full model through a visual Journey Builder, real-time Smart Segments, incoming webhooks, and per-contact history, Emercury is built to make the data structure work for you without gating features or handing you off to a bot. Start mapping your next automated flow to the schema, and build it on infrastructure designed for reliable, behavior-driven sends.
FAQs
1. What is a drip campaign data structure? A drip campaign data structure is the set of connected database tables and execution rules that run automated email sequences. It stores the campaign definition, the email templates, the workflow logic, each contact, the live position of every contact in the flow, and a history of every event. Together these let one flow serve millions of contacts reliably.
2. What database tables does an email automation workflow need? An email automation workflow typically needs seven tables: campaigns, templates, workflows, workflow steps, contacts, a contact queue, and event logs. Campaigns and templates hold reusable content. Workflows and steps hold the logic and order. Contacts hold recipient data. The queue tracks live journey state, and the event log records every send, open, and click.
3. How is the delay between drip emails stored in the database? The delay between drip emails is stored as an integer number of seconds on the workflow step, often called delay_seconds. A one-day wait is stored as 86400. The engine adds this delay to the trigger time to calculate an exact execute_at timestamp. Storing delays as data rather than live timers keeps long sequences reliable even if the system restarts.
4. What is the difference between a workflow and a campaign in the schema? A campaign defines the overall strategy and groups related workflows, while a workflow defines the specific trigger and the ordered sequence of steps that run. One campaign, such as onboarding, can contain several workflows. The campaign is the parent record, and each workflow references it through a campaign_id foreign key that ties the pieces together.
5. How does the system know where each contact is in a drip sequence? The system knows where each contact is through the contact queue table. Each row links a contact to a workflow, records the current step, stores an execute_at timestamp for the next action, and holds a status. To find why someone received an email or when the next one fires, you read this row. It is the live state of the entire automation.
6. Is a drip campaign a state machine or a directed acyclic graph? A drip campaign is usually modeled as both. The workflow definition is a directed acyclic graph, meaning ordered steps with dependencies and no loops back on themselves. Each contact is a state machine walking that graph, sitting in one current step that changes as events occur. Combining the two gives a clear definition and simple per-contact state.
7. How is conditional branching stored in a drip workflow? Conditional branching is stored using decision steps that carry a condition and multiple next-step pointers. A branch step evaluates something like whether a contact opened the last email, then routes them to a different step based on the result. Complex flows often store these as rows in a transitions table with a from-step, a condition, and a to-step.
8. What data types should I use for a drip campaign schema? Use UUIDs or big integers for identifiers, enumerations for status and event types, integers for delays in seconds, and timestamps for scheduling fields. Store flexible attributes in a JSON column or a key-value table. Keep all times in a single timezone such as UTC and convert only for display. These choices prevent time and status bugs.
9. How are email events like opens and clicks stored? Email events are stored as individual append-only rows in an event log table. Each row records the contact, the message or template, the event type such as opened or clicked, and a timestamp. Storing events as rows rather than counters lets you rebuild any metric later, branch a workflow on behavior, and keep an accurate compliance audit trail.
10. How do webhooks trigger an automation workflow? Webhooks trigger a workflow by sending a JSON payload that names the event and identifies the contact. The platform reads the payload, matches the event to a workflow trigger, confirms the contact meets the criteria, and inserts a row into the contact queue at the first step. This lets external systems start a sequence within seconds of a real action.
11. How do you prevent duplicate emails in an automated sequence? You prevent duplicates by making the send process idempotent, which means handling the same instruction twice produces one result. Attach a unique identifier to each send, usually a combination of contact and step, and add a unique database constraint. Before sending, the engine checks whether that identifier was already processed and skips it if so.
12. What is the Contact_Queue and why does it matter? The contact queue is the table that tracks exactly where each contact sits in a workflow and when the next event fires. It matters because it holds the live state of the whole system. Efficient engines only scan queue rows whose execute_at has arrived, so the queue is what lets automation scale without checking every contact on every cycle.
13. How does segmentation data connect to drip workflows? Segmentation data connects through the contacts table and its custom fields, plus the event log. A segment is essentially a query across contact attributes and past behavior. Real-time segments track entry and exit as data changes, and a workflow can use a segment as a trigger or a branch condition. Richer contact data makes both segmentation and personalization stronger.
14. How is unsubscribe and suppression data stored? Unsubscribe and suppression data is stored as a suppression list table plus consent fields on the contact record and an unsubscribe event in the log. The suppression list holds addresses that must be excluded, and the engine checks it immediately before every send. Consent fields record whether and when a contact agreed to receive email, which supports legal compliance.
15. Should drip campaign data live in one table or many? Drip campaign data should live in many connected tables rather than one. Separating campaigns, contacts, queue state, and events by function keeps queries fast and the model flexible. A single giant table forces the system to scan unrelated data on every operation and makes changes risky. Foreign keys join the tables when the engine needs a complete picture.
16. How does the schema support personalization and merge fields? The schema supports personalization by storing merge placeholders in templates and matching attributes in the contact custom fields. At send time, the engine renders the template and replaces each variable, such as first name or plan tier, with the value from that contact. Because personalization is a property of the data, richer contact fields directly enable more relevant emails.
17. How many emails should a drip sequence contain? A drip sequence usually contains three to seven emails, but the right number depends on the goal and the audience. Structurally, each email is one send step in the workflow, so adding or removing emails means adding or removing step rows. Focus on relevance at each step rather than length, and let engagement data guide how many steps to keep.
18. How do you measure drip campaign performance from the data? You measure performance by querying the event log for sends, opens, clicks, and conversions, then joining to revenue where available. Because events are stored as rows with timestamps, you can calculate open rate, click rate, and revenue per subscriber for any step or time window. Row-level data also lets you attribute results to specific steps in the flow.



