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.
| Type | Fields | What it does |
|---|---|---|
send | template, next | Renders 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. |
wait | days / hours / minutes, next | Waits. Sandbox accounts divide waits by their time scale. |
branch | if, then, else | if is one of { clicked: <send step> }, { event: <name>, within: { days: 3 } }, { property: <name>, equals: <value> }. |
split | percent, a, b | Sends percent% of people to a, the rest to b. |
update | properties, next | Sets contact properties. |
webhook | url (https), next | POSTs the enrolment to your URL. |
exit | Ends 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:
| Rule | Meaning |
|---|---|
invalid_field, unknown_step_type | The document does not match the schema (unknown fields are errors). |
key_mismatch | The document's key differs from the key you saved it under. |
unknown_step | start, next, then, else, a or b points at a step that does not exist. |
unreachable_step | No path from start reaches the step. |
cycle_without_wait | A loop with no wait in it would run forever. |
invalid_wait, wait_too_long | A wait with no duration, or over 365 days. |
clicked_not_a_send | A clicked branch names a step that is not a send. |
webhook_not_https | Webhook URLs must be https. |
unknown_brand, unknown_subscription_type, unknown_template | A reference that does not exist yet: create it first. |
template_brand_differs (warning) | The template was written for another brand. |
Before it goes live
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).journey_simulate: the path a real contact (contactId) or sample data would take, withevents(afterDays) andclickedsend steps. Nothing is sent.journey_request_activation: an agent key gets{ approvalRequired, approvalId, consoleUrl }and an owner approves; an owner key activates. The previous active version drains.- Afterwards:
journey_funnelper step,enrolment_tracefor one person (why they did or didn't get each email).