Ad Journeys — Sequential & Branching Ad Campaigns

Ad Journeys let you run multi-step ad sequences across newsletter audiences. Each step delivers a different creative to subscribers who already engaged with the previous one — no opt-in required, no ESP changes needed. Choose between a Linear Steps flow or a fully customisable Graph Builder canvas with branching, A/B testing, wait nodes, and more.

What is an Ad Journey?

An Ad Journey is a sequential campaign that guides subscribers through a branded narrative across multiple newsletter opens. MailAdx handles the sequencing, eligibility checks, and delivery automatically.

Common use cases:

  • Awareness → Consideration → Conversion: introduce your brand, then deepen with product proof, then close with an offer
  • Conditional funnels: show different creative to clickers vs. non-clickers at each step
  • A/B split tests: automatically compare two creative paths and declare a winner
  • Onboarding flows: Welcome → Setup tip → Success story with skip logic

Journey modes: Linear Steps vs. Graph Builder

When creating a new journey, choose between two modes:

FeatureLinear StepsGraph Builder
SetupSimple form, 2–5 stepsVisual drag-and-drop canvas
BranchingNoYes — BRANCH and AB_SPLIT nodes
A/B testingNoYes — AB_SPLIT with automatic winner declaration
Wait logicminWaitDays per stepWAIT node with 6 wait modes
AnalyticsPer-step funnelPer-node stats with graph overlay
Max steps/nodes5 stepsUp to 50 nodes, max depth 10

Linear Steps (Phase 1)

The simplest way to get started. A journey has 2–5 steps in sequence. Each step requires a creative ID and an optional minimum wait period.

How subscribers advance through steps

A subscriber advances from step N to step N+1 when:

  1. They received an impression of step N
  2. The minimum wait period (minWaitDays) has elapsed — or they clicked the ad (click bypasses the wait)

Advancement runs automatically in the background every 15 minutes. On each subsequent newsletter send, MailAdx checks the subscriber's journey state and serves the correct step.

Graph Builder (Phase 2)

The Graph Builder lets you design non-linear journeys using an interactive canvas. Drag nodes from the left palette, connect them with edges, and configure each node's behaviour in the right-hand panel.

Node types

NodePurpose
ENTRYRequired starting node. Every journey must have exactly one ENTRY node.
AD_STEPServing node — delivers a creative to the subscriber. Requires a Creative ID in config.
WAITHolds the subscriber until a condition is met (time, click, date). See wait modes below.
BRANCHEvaluates a signal and routes to a yes or no outgoing edge.
AB_SPLITRandomly assigns subscribers to arm_0 or arm_1. Assignment is permanent for the subscriber's journey lifetime. Automatically declares a winner after 500 impressions.
GOALTerminal node — marks the subscriber as journey complete when reached.
EXITTerminal node — removes the subscriber from the journey without reaching the goal.
MERGEConverges multiple paths into one (join node). Subscribers continue when they arrive from any arm.
HOLDOUTSplits traffic into a serving path and a holdout (control) path. Holdout subscribers receive no ad but are still tracked.
WEBHOOKFires a POST to an external URL (non-blocking). Journey continues immediately.
LOOPSends the subscriber back to a specified AD_STEP node. Useful for re-engagement sequences.

GOAL node types

A GOAL node is a terminal node that marks a subscriber as goal achieved when reached. Configure the goal type in the right-hand panel:

Goal typeBehaviour
CLICKSubscriber exits with GOAL_MET when they reach this node via a clicked path (default goal type)
IMPRESSIONSubscriber exits with GOAL_MET on reaching this node, regardless of click
CONVERSIONSubscriber is held at CONVERSION_PENDING until the advertiser postback pixel fires. Once the pixel fires, status upgrades to GOAL_MET. Subscriber re-queues every 15 minutes until converted.
CUSTOMJourney fires an HTTP POST webhook to your endpoint. 2xx response = GOAL_MET; non-2xx = journey completes anyway with status JOURNEY_COMPLETED.

Conversion postback pixel (GOAL type CONVERSION)

When you activate a journey with a CONVERSION goal, MailAdx generates a unique pixel URL. Embed this pixel (1×1 transparent GIF) in your post-purchase confirmation page or email:

<img src="https://api.mailadx.com/api/v1/cv/{pixelToken}?eh={sha256EmailHash}&v={valueUsdCents}" width="1" height="1" />
  • pixelToken: journey-specific token, available in journey detail → Conversion Pixel panel
  • eh: SHA-256 hash of the subscriber's email address (lowercase, no salt)
  • v: optional conversion value in USD cents (e.g. 4999 for $49.99)

The pixel endpoint is public, requires no auth, and returns Cache-Control: no-store. Conversion data is stored in MongoDB with a 90-day TTL and queryable via the Conversions tab in Journey Reports.

You can regenerate the pixel token at any time from journey settings — the old token immediately becomes invalid.

Custom goal webhook (GOAL type CUSTOM)

Configure a webhookUrl and optional webhookSecret on the GOAL node. When a subscriber reaches the GOAL node, MailAdx fires:

POST {webhookUrl}
Content-Type: application/json
X-Journey-Secret: {webhookSecret}   // only if configured

{
  "journeyId": "jrn-abc123",
  "emailHash": "sha256...",
  "nodeId": "goal1",
  "timestamp": "2026-07-01T12:00:00Z"
}

A 2xx response marks the subscriber GOAL_MET. A non-2xx or network timeout marks them JOURNEY_COMPLETED instead. Timeout is 5 seconds.

BRANCH signals

BRANCH nodes evaluate one of 13 signals to decide which path a subscriber takes. Signals default to false (pessimistic) if data is unavailable.

SignalDescription
CLICKED_NODESubscriber clicked a specific AD_STEP node (signalNodeId)
SERVED_NODESubscriber received an impression at a specific AD_STEP node (signalNodeId)
CLICK_WITHIN_DAYSSubscriber clicked any journey ad within the past N days (signalThreshold)
IN_SEGMENTSubscriber is in a named retargeting/audience segment (signalValue)
IMPRESSION_COUNT_GTESubscriber has received ≥ N total journey impressions (signalThreshold)
DAYS_SINCE_ENTRYAt least N days have passed since subscriber entered the journey (signalThreshold)
DEVICESubscriber's email client device matches (signalValue: MOBILE or DESKTOP)
GEO_COUNTRYPublisher's audience country ISO code matches (signalValue)
PUBLISHER_SIZE_GTEPublisher subscriber count ≥ threshold (signalThreshold)
LOOP_ITERATIONSubscriber is on their Nth loop iteration (signalThreshold)
HAS_CONVERTEDAdvertiser postback pixel has fired for this subscriber in this journey
CLICK_RATE_GTESubscriber's CTR across this journey ≥ N% (signalThreshold 0–100)
DAYS_IN_FUNNELTotal calendar days the subscriber has been in this journey ≥ N days (signalThreshold)

Compound branch conditions (AND/OR)

A BRANCH node can evaluate multiple signals combined with AND or OR logic. In the Graph Builder, click Add condition in the BRANCH config panel to add more conditions. When two or more conditions are present, an AND/OR toggle appears.

  • AND: subscriber is routed yes only if all conditions are true (short-circuits on first false)
  • OR: subscriber is routed yes if any condition is true (short-circuits on first true)
  • A single condition behaves identically to the original single-signal format (fully backward compatible)

WAIT modes

ModeBehaviour
FIXED_DAYSWait N calendar days
UNTIL_CLICKWait until the subscriber clicks an ad (or timeout)
UNTIL_DATEWait until a specific calendar date
BUSINESS_DAYSWait N business days (Mon–Fri, skips weekends)
RANDOM_RANGEWait a random number of days between min and max
UNTIL_NEXT_SENDWait until the publisher's next tracked newsletter send

A/B split testing

Add an AB_SPLIT node to randomly route subscribers to one of two paths (arm_0 and arm_1). Each subscriber's arm is locked permanently for the duration of the journey. MailAdx automatically tracks impressions and clicks per arm and declares a winner when:

  • Either arm has ≥ 500 impressions, and
  • The CTR difference between arms is ≥ 0.5% (absolute)

Once a winner is declared, all new subscribers are routed to the winning arm. Existing subscribers keep their original assignment.

Re-entry and cooldown

By default, subscribers who complete or exit a journey must wait 30 days before they can re-enter. This is configurable per journey (reEntryDays). On re-entry, A/B arm assignments are re-randomised.

Graph constraints

  • Exactly one ENTRY node per journey
  • At least one AD_STEP node
  • Maximum 50 nodes total
  • Maximum graph depth: 10 (longest path from ENTRY to any node)
  • All node IDs must be unique; all edge source/target IDs must reference existing nodes

CPM pricing

You set one base CPM per journey. The system auto-calculates per-step CPMs using a progressive multiplier — later steps bid higher to reach the most engaged subscribers:

  • Step 1: 1.0× base CPM
  • Step 2: 1.5× base CPM
  • Step 3: 2.0× base CPM
  • Step 4: 2.5× base CPM
  • Step 5+: 3.0× base CPM

For graph journeys, AD_STEP nodes are assigned multipliers in BFS order from ENTRY. You can override any node's CPM with a cpmOverride value in the node's config.

Setting up your first journey

  1. Navigate to Dashboard → Ad Journeys
  2. Click New Journey. Set a name and base CPM
  3. Choose Linear Steps (simple, 2–5 steps) or Graph Builder (advanced canvas)
  4. For linear steps: add 2–5 steps with creative IDs and wait periods
  5. For graph: drag nodes from the left palette, connect them, and configure each node
  6. Optionally set start/end dates, total budget, and re-entry days
  7. Click Activate

Budget and billing

You are billed only for AD_STEP nodes that actually serve on each open. There is no charge for subscribers held at WAIT nodes or siphoned to HOLDOUT paths.

An optional Total Budget (USD) can be set on the journey. For linear journeys, MailAdx splits the budget evenly across all steps. For graph journeys, the budget is shared across all AD_STEP line items with per-node pacing.

Analytics and reporting

Journey-level reporting is available in two places:

  • Journey detail panel (Ad Journeys → [journey] → expand row): inline per-step/per-node stats and a graph overlay
  • Journey Reports page (Ad Journeys → Journey Reports): dedicated reporting view with richer analysis

Journey Reports page

Select any journey from the left sidebar to see:

  • KPI summary: total impressions, clicks, overall CTR, total spend
  • Funnel tab: horizontal impression funnel across AD_STEP nodes; CTR bar chart color-coded by A/B arm when an AB_SPLIT is present
  • A/B Test Breakdown (appears automatically for graph journeys with AB_SPLIT nodes): per-arm impressions, clicks, CTR, and spend as comparison bars; statistical signal strength indicator; impression threshold progress bar (goal: 500 per arm); winner trophy badge once declared
  • Node Performance tab: sortable table with per-node delivery metrics; A/B arm column shown when applicable
  • Conversions tab: total conversion count, total conversion value, and last 20 conversion events with timestamp

A/B statistical signal

Signal strength is assessed once both arms reach 500 impressions:

  • Strong signal: absolute CTR lift ≥ 1%
  • Moderate signal: absolute CTR lift 0.5–1%
  • No significant difference: lift < 0.5%
  • Insufficient data: either arm below 500 impressions (progress bar shown)

Pausing and resuming

Journeys can be paused at any time. Pausing halts delivery of all AD_STEP nodes immediately. Resuming restores delivery — subscribers retain their node position and continue from where they left off.

API reference

MethodPathDescription
GET/api/v1/dsp/journeys/cpm-previewPreview CPMs per step before creating
POST/api/v1/dsp/journeysCreate a DRAFT journey (linear or graph)
GET/api/v1/dsp/journeys/{id}Fetch journey details (includes nodes/edges for graph journeys)
PATCH/api/v1/dsp/journeys/{id}Update name, CPM, dates, steps, or graph
POST/api/v1/dsp/journeys/{id}/activateActivate a DRAFT or PAUSED journey
POST/api/v1/dsp/journeys/{id}/pausePause an ACTIVE journey
POST/api/v1/dsp/journeys/{id}/resumeResume a PAUSED journey
POST/api/v1/dsp/journeys/{id}/archivePermanently stop a journey
GET/api/v1/dsp/journeys/{id}/statsPer-step delivery data (linear)
GET/api/v1/dsp/journeys/{id}/node-statsPer-node delivery data (graph journeys)
POST/api/v1/dsp/journeys/{id}/validate-graphValidate a graph structure without saving
POST/api/v1/dsp/journeys/{id}/debug-advanceManually trigger subscriber advancement (dev/staging only)
POST/api/v1/dsp/journeys/{id}/regen-pixelRegenerate conversion pixel token (invalidates previous token immediately)
GET/api/v1/dsp/journeys/{id}/conversionsList conversion postback events for this journey
GET/api/v1/cv/{pixelToken}?eh={emailHash}&v={valueUsdCents}Public conversion pixel endpoint — returns 1×1 GIF, records conversion

Graph journey request format

To create a graph journey, include nodes and edges arrays in the POST body instead of steps:

POST /api/v1/dsp/journeys
{
  "name": "My Branching Journey",
  "baseCpm": 8.00,
  "nodes": [
    { "id": "entry1", "type": "ENTRY", "label": "Start", "canvasX": 100, "canvasY": 100, "config": {} },
    { "id": "ad1",    "type": "AD_STEP", "label": "Step 1", "canvasX": 300, "canvasY": 100,
      "config": { "creativeId": "cre-abc123" } },
    { "id": "branch1","type": "BRANCH", "label": "Clicked?", "canvasX": 500, "canvasY": 100,
      "config": { "signal": "CLICKED_NODE", "signalNodeId": "ad1" } },
    { "id": "ad2",    "type": "AD_STEP", "label": "Engaged", "canvasX": 700, "canvasY": 50,
      "config": { "creativeId": "cre-def456" } },
    { "id": "ad3",    "type": "AD_STEP", "label": "Re-engage", "canvasX": 700, "canvasY": 150,
      "config": { "creativeId": "cre-ghi789" } },
    { "id": "exit1",  "type": "EXIT", "label": "Done", "canvasX": 900, "canvasY": 100, "config": {} }
  ],
  "edges": [
    { "id": "e1", "sourceId": "entry1",  "targetId": "ad1" },
    { "id": "e2", "sourceId": "ad1",     "targetId": "branch1" },
    { "id": "e3", "sourceId": "branch1", "targetId": "ad2",   "handle": "yes" },
    { "id": "e4", "sourceId": "branch1", "targetId": "ad3",   "handle": "no" },
    { "id": "e5", "sourceId": "ad2",     "targetId": "exit1" },
    { "id": "e6", "sourceId": "ad3",     "targetId": "exit1" }
  ]
}

Limits

  • Linear: minimum 2 steps, maximum 5 steps
  • Graph: minimum 1 AD_STEP node, maximum 50 nodes, max path depth 10
  • Minimum minWaitDays per step: 0 (same send eligible) up to 90 days
  • Supported formats: NATIVE and STANDARD (image banner); formats can be mixed across nodes
  • One AD_STEP node served per subscriber per newsletter send
  • A/B winner declared after ≥ 500 impressions with ≥ 0.5% CTR lift
  • Default re-entry cooldown: 30 days (configurable per journey)