Workflows
Automated multi-step journeys for contacts, started by events, segments, schedules, or by hand
A workflow is an automation modelled as a graph: contacts advance step by step — receiving emails, pausing, taking conditional branches, hitting webhooks, having their data updated, or exiting. Every contact travels the graph on their own, in an independent workflow execution.
Triggers
Each workflow has exactly one trigger, which determines how contacts get in:
| Trigger type | When it fires |
|---|---|
EVENT | Whenever a matching event lands on a contact — a custom event from /v1/track, a system event like email.received or contact.subscribed, or a segment entry/exit event. |
MANUAL | Never on its own; only when you start an execution yourself via POST /workflows/:id/executions. |
SCHEDULE | On a recurring cadence described by triggerConfig (a cron expression, for instance). |
Workflows created through the API default to an EVENT trigger using the event name you supplied. Switching to MANUAL or SCHEDULE is done afterwards, by updating triggerType and triggerConfig with PATCH /workflows/:id.
Once a workflow has run at least once, its trigger event name is frozen — choose it deliberately. Everything else about the configuration (step delays, in-step conditions) stays editable.
Lifecycle and the enabled flag
Every workflow starts life with enabled: false, and nothing executes until you set enabled to true. If a workflow seems to never fire, check this flag first — it's the usual culprit.
Whether a contact may pass through the same workflow twice is governed by allowReentry (off by default). Keep it disabled for one-time sequences like welcome or onboarding emails; enable it when the journey should be repeatable, such as recurring nudges or re-prompts.
Step types
The first step of any workflow is a TRIGGER step, created automatically. Everything after it is built by adding steps and wiring them together with transitions (the graph's edges).
| Step type | Purpose | Required config |
|---|---|---|
TRIGGER | The auto-created entry point, carrying the trigger configuration. | eventName (for EVENT trigger) |
SEND_EMAIL | Emails the contact from a template; variables are filled from contact data plus the execution context. | templateId, optional from override |
DELAY | Holds the execution for a set amount of time before moving on. | amount, unit (minutes / hours / days) |
WAIT_FOR_EVENT | Holds the execution until a given event is tracked on the contact, falling through on timeout. | eventName, timeout (seconds) |
CONDITION | Splits the path based on contact or event data; every CONDITION step has two outgoing transitions labelled yes / no. | A filter expression (same shape as segment filters) |
WEBHOOK | POSTs contact and execution context as JSON to an external HTTPS endpoint. {{variables}} work in url, header values, and body. | url, optional method, headers, body |
UPDATE_CONTACT | Applies a patch to the contact's data — a common way to tag progress ({ stage: "activated" }). | data object |
EXIT | Ends the execution, optionally noting an exitReason for later analysis. | optional reason |
Transitions
The edges connecting steps are called transitions. Most of the time a transition just means "go here next"; on CONDITION steps, each outgoing transition also carries a branch value ("yes" or "no") that tells the engine which way to route once the condition is evaluated.
Removing a step from the middle of a chain? Add ?splice=true to DELETE /workflows/:id/steps/:stepId and the neighbouring transitions are stitched back together automatically — without it, the graph is left with a gap.
Executions
A WorkflowExecution is created for every contact that enters. Its possible states:
| Status | Meaning |
|---|---|
RUNNING | A step is being processed, or is about to be. |
WAITING | Held inside a DELAY or WAIT_FOR_EVENT step. |
COMPLETED | Made it to the end of the graph without issues. |
EXITED | Reached an EXIT step; the cause is stored in exitReason. |
FAILED | Stopped by an unrecoverable error — for instance a webhook still non-2xx after retries, or a missing template. |
CANCELLED | Terminated by hand through the API or dashboard. |
Every execution has its own context JSON object. During template rendering and condition evaluation, it's merged into the contact's data. Manual starts via POST /workflows/:id/executions accept an initial context, which is the mechanism for per-execution variables — a coupon code, a referrer's name — that shouldn't be persisted on the contact itself.
Locking active workflows
While any execution is active (RUNNING or WAITING), the workflow is locked against structural changes. Before making a breaking edit — adding or removing steps, changing the trigger event, switching trigger type — do one of the following:
- Turn the workflow off and let the in-flight executions finish, or
- Call
POST /workflows/:id/executions/cancel-allto abort everything currently running.
Non-structural edits — renaming the workflow, changing the content of templates its steps reference — are allowed at any time.
Common patterns
- Welcome series — trigger on the
signed_upevent, send the welcome email, wait 2 days, send a tips email, wait 5 more days, then nudge toward an upgrade. - Inbound auto-reply — trigger on
email.received, use aCONDITIONcheckingevent.spamVerdict == "PASS", thenSEND_EMAILwith the auto-reply template. See Receiving emails. - Re-engagement — start from a segment exit (
segment.active-users.exit), send a "we miss you" message, wait up to 7 days for anemail.openedevent, then branch on whether they engaged. - Webhook fan-out — trigger on
purchase.completed, hit your CRM with aWEBHOOKstep, tag the contact viaUPDATE_CONTACT({ tier: "customer" }), and finish with aSEND_EMAILreceipt.
API reference
Everything runs through the /workflows API, which covers the workflow itself plus its steps, transitions, and executions. The full route list is in the API overview.