Your 12-Step Customer.io Workflow Should Be Three Small Workflows Linked by Send Event

I've opened the same screen on more client accounts than I can count. A single Customer.io workflow, inherited from whoever ran lifecycle before, with a dozen branches fanning out across the canvas. Somewhere in there is a wait, a couple of conditional splits, an exit rule that fires on conversion, and three message blocks that may or may not still be relevant. Nobody currently on the team built it. Nobody is quite sure what happens to a person who enters on a Tuesday versus a Thursday. So nobody touches it, and the workflow quietly calcifies.

That workflow is doing too much. It started as a welcome series, picked up a re-engagement arm, absorbed a win-back branch, and grew a feature-adoption nudge when someone asked for one. Each addition made sense on its own. Together they made a tree nobody can read.

The fix isn't a tidier tree. It's fewer trees. Customer.io has a block that lets one workflow hand off to another by firing a named event, and most teams have never used it for that. This post is about the Send Event action: what it does, three cross-campaign architectures it makes possible, the naming conventions that keep them auditable, and the cases where splitting is the wrong call.

The 12-step workflow problem, and where Send Event fixes it

The problem with a big workflow isn't performance. It's that branching scales badly for the humans who maintain it. Every conditional split doubles the number of paths a person can take, and the visual editor can only show so much before you're scrolling sideways trying to trace one route. The person who built it holds the logic in their head. When they leave, the logic leaves with them.

Splitting beats branching because each small workflow has one job and one entry condition. You can read it in ten seconds. You can change the win-back copy without worrying you've broken activation. A new hire can open the engagement program and understand it without first reverse-engineering the eleven branches that precede it.

This is the same argument I made for activation sprints over bloated drips. A three-email activation sprint that hands off via Send Event beats a twelve-email welcome series on conversion and deliverability, and the handoff is the part that post left mostly implicit. Here I want to make it explicit, because the handoff mechanism is the thing that unlocks the whole pattern. (And no, I won't use that word again.)

If you've read the guide to building Customer.io Journeys that convert, think of this as the architectural layer above it: not how to build one good workflow, but how to connect several so the whole program stays legible.

What the Send Event block actually does

The Send Event block creates an event from inside a campaign. An event in Customer.io is a name plus a data object, and once it exists it behaves exactly like an event you'd send through the API or the JavaScript snippet. According to Customer.io's documentation on sending events from a workflow, the block can trigger other campaigns or add people to segments, all without writing code or talking to a developer.

A few specifics matter for the architectures below.

The event carries data. A Send Event action has a size limit of 100 KB per event. That's plenty to carry what the next workflow needs: the plan the person chose, the feature they activated, the account they belong to. The metadata travels with the event, so the downstream workflow reads it from the trigger rather than re-fetching attributes from the people store.

You can send the event for someone other than the person in the campaign. By default the event fires for the current person, but you can pick "Someone else", choose the workspace they belong to, and set the value that identifies them. If that person doesn't exist yet, the action creates them. That's how you trigger a message to an account owner from inside a campaign running on a different user.

It works from any campaign type. The same action lives in webhook-triggered campaigns, where it does the heavy lifting. A webhook campaign has no person attached to it, so it can't send a message on its own. Per Customer.io's webhook campaign docs, you use Send Event to turn incoming webhook data into an event tied to a real person, which then triggers a second campaign that does the messaging. Send Event is, in effect, a webhook into your own workspace.

One caveat on the payload. Three keys inside the data object are reserved: recipient, from_address, and reply_to. Customer.io's events documentation notes these override the corresponding email fields, so don't reuse those names for your own metadata unless you mean to change who the email goes to.

Architecture 1: the activation-to-engagement handoff

Split your activation program and your engagement program into two workflows, and connect them with a single Send Event.

The activation workflow has one job: get the new user to their first real outcome. It runs three or four messages, exits the moment the person activates, and ends with a Send Event named something like activation.completed. The engagement workflow triggers on that event. It never starts for anyone who didn't activate, because the event only fires on the exit you defined.

Why is this better than one workflow with an exit-on-conversion branch? Because the branch version forces every future change through one canvas. Want to add a second engagement track for power users? In the combined workflow, that's another split bolted onto an already-busy tree. In the two-workflow version, it's a new campaign listening for the same activation.completed event, and you can build and test it without opening the activation program at all.

The metadata makes it clean. When activation fires the event, it includes what the person did to activate. The engagement workflow reads that from the event and tailors its first message, no attribute lookups, no Liquid gymnastics against possibly-stale fields.

Where this fails: a tiny program with a single path and no branching. If your activation flow is three messages with no engagement follow-up, splitting it buys you nothing but an extra campaign to maintain. Keep it as one workflow.

Architecture 2: the parallel campaign fanout

One event triggers two or more campaigns running on different cadences, each with its own purpose.

Customer.io's docs are explicit that you can send events from any campaign to trigger parallel campaigns. So when a trial converts, you fire trial.converted once and two campaigns pick it up: an onboarding drip that teaches the product over two weeks, and a short sales-led nudge that books an implementation call in the first 48 hours. Different cadences, different goals, different copy, one trigger.

The reason to run these as separate campaigns rather than parallel branches in one workflow is that each campaign keeps its own settings and its own metrics. You can suppress the sales nudge for self-serve plans without touching the onboarding drip. You can read the conversion on each independently instead of squinting at one report that blends them. And when the sales team wants to change their cadence, they edit their campaign, not yours.

The fanout also scales to groups. If your event needs to reach everyone on an account rather than one user, a webhook campaign's Batch Update action can send an event to up to 1,000 people matching your criteria. One account-level change, fanned out to every seat.

Architecture 3: the lifecycle state machine

Model each lifecycle state as its own workflow, and end each one with a Send Event that names the state the person just moved into.

Trial, activated, engaged, at-risk, churned. Each is a small workflow. The activated workflow ends with lifecycle.state_changed carrying new_state: "engaged". The engaged workflow triggers on that, runs its course, and ends by firing the same event with new_state: "at_risk" when its exit condition hits. The state lives in the event name and payload, not in a forest of attribute checks inside one mega-workflow.

Conversion is the number to watch here. Customer.io's State of Customer Messaging and Engagement 2026, a survey of more than 750 practitioners, found 66% of teams now track conversion rates as their primary success metric rather than open rates. A state machine gives you a clean place to measure each transition against that number, because every state has its own small workflow and its own outcome.

This pairs well with two features I've written about. Measure each state transition with Goals, but keep them separate from your conversion events, because Customer.io's Goals are not conversion events and treating them the same double-counts revenue. And before you trust the lift from any state's messaging, put a holdout on it. The holdout group is now a checkbox in Customer.io, so there's no excuse for not measuring whether the engaged-state campaign actually moves anyone.

A data campaign can drive the transitions. If a state change depends on evaluating an attribute you can't reference directly, you run a data campaign that checks the condition and fires the state-change event. That keeps the evaluation logic in one place and the messaging in another.

Four event-naming conventions that pay off

Name cross-campaign events with a namespace, lowercase, and a consistent separator. The exact scheme matters less than picking one and holding to it.

Four that earn their keep: lifecycle.state_changed for state machine transitions, activation.completed for the activation handoff, engagement.tier_changed when someone moves between engagement bands, and program.exited when a person leaves a program for any reason. The namespace before the dot tells you which system owns the event. The verb after it tells you what happened.

This matters more as volume grows. When you have forty campaigns and a hundred event types, state_changed is ambiguous and lifecycle.state_changed is not. Good event naming is the same discipline as good event schema design, and the teams that skip it end up with events called event2_final_v3 that nobody dares delete.

Events can also feed segments, not just campaigns. A engagement.tier_changed event can drop someone into a data-driven segment the moment it fires, which is often cleaner than re-deriving the tier from raw attributes. If you're building segments off behaviour, the same naming discipline applies there too, and the advanced segmentation guide covers how to structure them.

When not to split

Don't split tiny programs. A flow with under three messages, a single channel, and one linear path is easier to read as one workflow than as two connected by an event. Splitting it adds an event to name, a second campaign to maintain, and a handoff to debug, for no gain in clarity.

The cost of splitting is real: more campaigns in the list, more event names to keep straight, and more places to look when something goes wrong. That cost is worth paying when a workflow has genuinely distinct phases, different cadences, or different owners. It's not worth paying to break a simple linear sequence into pieces because an article told you to.

Event noise is the other risk. Every Send Event is an event in your workspace, showing up in activity logs and timelines. Fire one on every minor step and you bury the signal. Reserve named cross-campaign events for real transitions, not for internal bookkeeping inside a single program.

A 60-minute exercise: split one workflow in two

Pick your most overloaded workflow and give yourself an hour. Open it and trace every path a person can take, writing each one down. You'll usually find two or three that have nothing to do with each other sharing the same canvas.

Find the natural seam. It's almost always a conversion or a state change: the point where the person stops being "new" and starts being "engaged", or where they convert and move to onboarding. That seam is where one workflow should end and the next should begin.

Build the second workflow as a new event-triggered campaign. Decide the event name (use the namespaced convention above), add a Send Event block at the seam in the original workflow, and point the new campaign's trigger at that event name. Move the post-seam messages into the new workflow. Test it with a single person from your sample data and watch the event fire and the second campaign pick them up. You've now got two readable programs where you had one unreadable one, and you've used the pattern you can repeat on the next workflow.

Frequently asked questions

Can a Customer.io Send Event trigger more than one downstream campaign?

Yes. An event triggers any campaign whose trigger is set to that event name. If three event-triggered campaigns are listening for trial.converted, one Send Event starts all three. That's exactly what makes the parallel fanout architecture work.

How much data can I pass in a Send Event payload?

Up to 100 KB per event, per Customer.io's documentation. That covers the event name and its entire data object. It's more than enough to carry the context the next workflow needs, so you rarely have to re-read attributes from the person's profile downstream.

Can I send an event for a different person from inside a campaign?

Yes. Choose "Someone else" in the Send Event setup, pick the workspace that person belongs to, and set the value that identifies them. If no person matches that identifier, the action creates them. This is how you message an account owner from a campaign that's running on a different user.

What's the difference between a Send Event in a regular campaign and one in a webhook campaign?

In a regular campaign, the event defaults to the current person and you can optionally redirect it to someone else. In a webhook-triggered campaign there's no person attached, so you must set an identifier from the incoming trigger data before the event can attach to anyone. The block looks the same; the difference is whether a person is already in context.

Does a Send Event count against my event volume in billing?

No. Customer.io bills on two things: profiles (people and objects) and emails sent, per the How We Bill documentation. Events aren't a billing dimension, so firing a Send Event doesn't add to any metered count. The one caveat: if the event targets a person who doesn't exist yet, it creates a profile, and profiles are billable.

How do I see which campaigns a Send Event triggered?

Open the affected person's activity log. The sent event appears in their timeline, and each campaign that triggered on it records the person entering shortly after. Reading the timeline top to bottom shows you the event and everything it set off.

Won't a Send Event accidentally re-trigger a campaign or create duplicates?

It depends on the downstream campaign's re-entry settings, which you control per campaign. If you're worried about duplicate events from an integration, you can pass an id formatted as a ULID with the event, and Customer.io will deduplicate so the same event isn't processed twice.

Can a Send Event add someone to a segment instead of triggering a campaign?

Yes. Events can add people to data-driven segments, so a Send Event can move someone into a segment the moment it fires. That's often cleaner than re-deriving the same membership from raw attributes across several conditions.

Should every lifecycle phase really be its own workflow?

No. Split when phases have distinct cadences, different owners, or branching that's hard to read in one canvas. Keep it as one workflow when the program is short, single-channel, and linear. The point is legibility, not a rule that everything must be split.

What naming convention should I use for cross-campaign events?

Use a namespace, lowercase, and a consistent separator: lifecycle.state_changed, activation.completed, engagement.tier_changed, program.exited. The namespace says which system owns the event; the verb says what happened. Consistency matters more than the specific scheme you pick.

Can a Send Event reach a different Customer.io workspace?

Yes. The action can send an event to another workspace, creating the person there if they don't already exist, per Customer.io's docs. That's useful for parent-and-child account structures or regional workspaces that need to stay in step.

Is the Send Event action the same as the Track API?

No. The Track API sends events into Customer.io from outside, usually from your own code. Send Event generates an event from inside a campaign with no code required. Both produce the same kind of event once it lands, but Send Event is the in-platform feature you use to connect campaigns to each other.

Sources

  • Send events from your workflow. Customer.io Documentation. Updated 24 November 2025. https://docs.customer.io/journeys/event-action/
  • Send an event from inside a campaign. Customer.io Documentation. Updated 10 December 2024. https://docs.customer.io/journeys/data-campaign-event-action/
  • Events. Customer.io Documentation. Updated 27 April 2026. https://docs.customer.io/journeys/events/
  • Webhook campaigns. Customer.io Documentation. Updated 9 January 2025. https://docs.customer.io/journeys/webhook-triggered-campaigns/
  • How We Bill. Customer.io Documentation. Updated 26 May 2026. https://docs.customer.io/accounts-and-workspaces/billing/
  • State of customer messaging and engagement 2026. Customer.io. Published 20 March 2026. https://customer.io/learn/omnichannel/customer-messaging-2026
David Crowther
Book a free consultation →

On our call, you'll be speaking with David Crowther, founder of NerveCentral.

Our initial consultation is not a sales call—you'll talk, I'll listen and ask questions—then I'll come back to you within 48 hours with our best ideas on how to grow your business.