Journey documents

A journey is one document (JSON, or the same document as YAML), saved as an immutable version: draft → active (one per journey; each template's version is pinned at activation) → draining (keeps its enrolments, takes no new ones) → stopped. Saving the same document again makes no new version. The schema is apps/api/src/modules/journey-document.ts; a worked example is examples/welcome/journey.yaml.

key: welcome                     # 2-63 lowercase letters, digits and dashes; must match the URL / tool key
name: Welcome series
brand: main                      # a brand key
subscriptionType: news           # one of the brand's subscription types: the consent every send checks
entry:
  on: consent_granted            # enter when a person gains consent for brand + subscriptionType
  # on: { event: signed_up }     # or: enter on a tracked event (they still need consent to be sent to)
reentry: never                   # each person enters once, ever (the only option today)
goal: { event: activated }       # optional: the event that counts as success; exits them (exit: true)
exitWhen:                        # optional, up to 20: leave as soon as one is true
  - { event: unsubscribed_everywhere }
  - { property: plan, equals: enterprise }
start: welcome                   # the first step's key
steps:
  welcome:   { type: send, template: welcome, next: wait-2d }
  wait-2d:   { type: wait, days: 2, next: clicked }          # days, hours and/or minutes; at most 365 days
  clicked:
    type: branch
    if: { clicked: welcome }     # a human click on the email sent at send step `welcome`
    then: next-steps
    else: nudge
  next-steps: { type: send, template: next-steps, next: done }
  nudge:      { type: send, template: nudge, next: done }
  done:       { type: exit }

Steps

Step keys: lowercase letters, digits, dashes and underscores. Every step except exit names where to go next.

TypeFieldsWhat it does
sendtemplate, nextRenders the template for the contact and sends it, if the eligibility check allows (consent, suppression, frequency cap, sandbox allowlist, verified sending domain, the reputation guard). A refusal is a skip with a reason; the enrolment still moves on. Over the sending domain's warm-up cap the step is deferred (warm_up): the enrolment waits at the step until the next day, then sends.
waitdays / hours / minutes, nextWaits. Sandbox accounts divide waits by their time scale.
branchif, then, elseif is one of { clicked: <send step> }, { event: <name>, within: { days: 3 } }, { property: <name>, equals: <value> }.
splitpercent, a, bSends percent% of people to a, the rest to b.
updateproperties, nextSets contact properties.
webhookurl (https), nextPOSTs the enrolment to your URL.
exitEnds the enrolment.

Validation

journey_validate (and every save) checks the schema, the graph and the references. Each error names the step (when there is one), the field, the rule and a fix:

RuleMeaning
invalid_field, unknown_step_typeThe document does not match the schema (unknown fields are errors).
key_mismatchThe document's key differs from the key you saved it under.
unknown_stepstart, next, then, else, a or b points at a step that does not exist.
unreachable_stepNo path from start reaches the step.
cycle_without_waitA loop with no wait in it would run forever.
invalid_wait, wait_too_longA wait with no duration, or over 365 days.
clicked_not_a_sendA clicked branch names a step that is not a send.
webhook_not_httpsWebhook URLs must be https.
unknown_brand, unknown_subscription_type, unknown_templateA reference that does not exist yet: create it first.
template_brand_differs (warning)The template was written for another brand.

Before it goes live

  1. journey_plan: the step diff against the active version, the template versions activation would pin, and how many people are in progress on the active version (they finish on it; nobody is migrated).
  2. journey_simulate: the path a real contact (contactId) or sample data would take, with events (afterDays) and clicked send steps. Nothing is sent.
  3. journey_request_activation: an agent key gets { approvalRequired, approvalId, consoleUrl } and an owner approves; an owner key activates. The previous active version drains.
  4. Afterwards: journey_funnel per step, enrolment_trace for one person (why they did or didn't get each email).