Skip to main content

Campaign journey pipeline

The campaign lifecycle page frames a campaign as one object with a status machine; the journey enrollment fan-out page details who gets in. This page covers what happens in between — the pipeline every enrolled contact moves through, from enrollment to measured funnel outcome, and the surfaces where you build and operate it: the journey builder at /campaigns/journey and the campaign execution view at /campaigns/:id. The same pipeline shape holds for all three modes — a fixed-set audience (blast and drip) enters in batch, a streamed audience (journey) enters one contact at a time — and from that point on the stages are identical: personalize, pick the channel, pass the gate, measure.

Dashboard anatomy: builder vs execution

Two dashboard surfaces share one campaign id:
  • /campaigns/journey — the builder. A visual canvas for the journey graph: a node palette (trigger, send, wait, branch, goal, exit), a graph validator that rejects cycles and unreachable nodes, a template picker, a dry-run simulator, and a goal-attachment panel. Everything you build here persists into the campaign’s journey definition; launching it is still the standard campaign launch.
  • /campaigns/:id — the execution view. Recipients and per-step analytics, the attribution funnel panel, holdout and lift panels, A/B results, link analytics, and the pause/resume controls. For a journey campaign, this is also where the per-node funnel overlay reads its numbers from.
The builder answers “what does the graph do”; the execution view answers “what did it do”. Both bind to the same cmp_ id, so clone, enroll, and analytics endpoints stay pointed at one campaign.

Pipeline stages

Every enrolled contact, however they entered, moves through the same five stages: 1. Enroll — entry into the audience. For a blast or drip, the whole audience resolves once at launch (a list, a segment, a filter, or all contacts). For a journey, enrollment is continuous and per-contact: inbound-reply keyword, segment entry/exit, CDP-track event, a rollout, or the single-contact enrollment endpoint behind the contact-360 enroll action. Every source funnels into the same guarded entry chain — offboard precheck, active-enrollment guard, re-entry policy, entry rate limit — detailed in journey enrollment fan-out. 2. Personalize — the template’s per-recipient variables resolve against the contact record ({{first_name}} and your custom fields), so one template renders a concrete message per contact. Channel branches in a journey graph — channelPreferenceBranch, smartChannel — pick the send channel from the contact’s preferences or engagement signal at this point, so the channel decision rides on the resolved profile. 3. Channel — the send dispatches over whatever channels the graph or campaign configuration picked: SMS, WhatsApp, email, voice, or the others your channels support. In a drip, each step carries its own channel; a journey graph has one send node per edge. Omnichannel fallback chains let one send attempt advance to a secondary channel when the primary fails to deliver. 4. Gate — the scheduling and safety gates all hold on the send path, tenant-owned and tenant-configured: the campaign’s throttle pace, quiet-hours windows in the recipient’s local timezone, frequency caps, send-time optimization (fixed or recipient-optimal), and the credit-cap governor. A gate defers or persists first — a contact in a quiet-hours window parks rather than failing — so gate outcomes show up in the funnel as a delay, not a drop. 5. Measure — outcomes persist per contact as they happen: sends, deliveries, link clicks, journey goal conversions, and terminal enrollment statuses. The funnel stage below is a read over those records, computed when you ask, not a batch job you wait on. The stages are per-contact, so in a journey you see all five in flight at once — one contact is enrolling while another waits at a gate and a third converts against the goal.

Branch semantics

The journey builder’s core node taxonomy is small enough to reason about as a pipeline:
  • trigger starts enrollment when its configured event or audience condition occurs.
  • delay pauses a contact until a duration or scheduled time elapses.
  • filter evaluates contact or event data and sends the contact down the matching path.
  • channelPreferenceBranch selects a channel from the contact’s recent engagement, the configured primary channel, and an ordered fallback list.
  • smartChannel lets the runtime select a channel from the contact’s inbound-channel signal, with the node’s fallback channel as its safety net.
  • action performs the next journey operation, such as sending a message, updating a contact, calling a webhook, or ending the journey.

Channel preference vs. smart channel

channelPreferenceBranch is an operator-authored preference chain. It checks the most recent engagement on any channel, then the node’s primaryChannel, then each fallbackChannels entry in order. It skips channels the contact has opted out of. One more input acts before any attempt: a channel your account’s Reputation Health Center currently scores critical is demoted behind every healthier channel in the chain (demoted, never dropped), and dry-run and pathing output expose the reordering; see the selection-time plane in The five fallback planes. A channel is unavailable when it has no matching outgoing handle, is opted out, or cannot be selected from the available preference signals; the node advances to the next configured channel and then to the default handle. If neither a matching handle nor default is wired, the runtime fans out to the configured outgoing edges rather than silently dropping the contact. The branch divides the journey when its channel handles lead to different downstream sub-flows. If those handles converge on the same next node, it only re-routes that one send decision; the contact remains on one journey path. A later delivery failure is a separate send-time fallback concern, covered by the cross-channel fallback model. smartChannel is different: the runtime chooses the channel from the contact’s most recent inbound channel signal, rather than walking an operator-defined engagement-and-preference chain. If there is no signal it uses the node’s configured fallback channel; if that channel has no matching edge it tries default, then the node’s outgoing edges as a safety net. Its channel-unavailable and per-hop fallback contract is documented in the cross-channel fallback model, rather than duplicated here.

Finding the fired branch in execution analytics

Branch choices are recorded against the branch node’s own id. In /campaigns/:id, read GET /campaigns/:id/journey/node-analytics: its nodes entries identify node_id and node_type and report entered, completed, dropped, and currently_at. Compare the channelPreferenceBranch or smartChannel entry with the downstream channel/action node entries to attribute where contacts left the flow. The endpoint is a contact-level funnel; use GET /campaigns/:id/journey-analytics for per-node message delivery totals, not for branch attribution.

Funnel metrics surface

Measurement in this pipeline is funnel-shaped: a counted base that progressively narrows through each next step.
  • Per-node contact funnel — GET /campaigns/:id/journey/node-analytics returns, for every node in the journey graph, the distinct contacts that entered it, completed it end-to-end, dropped out of it, and are currently parked at it. The builder overlays this on the canvas, so a node shows its own enter/convert counts. Zero-fills nodes with no activity yet, so the graph always renders every node.
  • Attribution funnel — GET /campaigns/:id/attribution-funnel answers “how many contacts moved through, and what is that worth”: touched (contacts the campaign sent at least one message to), clicked (touched contacts who clicked a tracked short link), converted (touched contacts who fired the campaign’s journey goal), and ordered (the converted subset whose conversion links to a commerce order), with per-stage drop-off rates and the converted cohort’s gross purchase value, send spend, and ROAS. It is deliberately allocation-model-free — the ROAS counterpart across contributing campaigns lives at GET /campaigns/:id/roas.
  • Message-level delivery stats — GET /campaigns/:id/journey-analytics reports per-node total/delivered/failed message rows, complementing the contact-level funnel when you need to know why a node lost contacts.
For the message statuses dripping under these counts, see delivery lifecycle.

API surface

Pipeline-interaction endpoints most integrations care about: Guides walk these calls end to end: build a journey in the canvas and send a campaign end-to-end. The campaigns API reference is the payload contract.