Build your first automation flow
By the end of this guide you will have shipped a real multi-channel flow — one that welcomes a new lead, branches on the sentiment of their reply, and routes the conversation to an AI agent or a human — and you will know how to debug it with the Executions API when a run misbehaves. This is a design walkthrough, not a node dictionary. The Flows overview covers the concepts and the full node-type table, and the Flow Builder reference lists every node, shortcut, and visual option. For each step below, the guide shows both the canvas path and the API-defined JSON.
1. The use case — welcome a new lead, route by sentiment
Sketch the narrative before touching the builder. You want to:- Start when a contact is created (web form, API, import).
- Send a welcome SMS.
- Wait 24 hours, then send a follow-up.
- Classify the contact’s reply with AI:
interested,question,complaint, orother. - Route
interestedandquestionreplies to your AI agent; alert your team in Slack on acomplaint; leaveotherfor manual follow-up.
2. Choose a trigger and shape the payload
Every flow starts with exactly one trigger. The common ones:
For the welcome flow, a Business Event trigger on
contact.created is the simplest choice:
- In the builder: drag a trigger onto the canvas, set the trigger type to Business Event, and pick
contact.created. - In the API definition:
trigger_typeiseventanddefinition.triggercarries the detail:
phone, email, first_name, last_name, and the rest of the contact record; a webhook trigger hands you the POSTed JSON body flattened the same way. Flat naming matters — see section 5 before you paste {{contact.first_name}} into a node.
Gate noisy triggers. contact.created fires on imports and API batches too, so for this design a Condition node checks source equals signup_form before anything is sent. A trigger filter you add after a flow has quietly SMS’d 50,000 imported contacts is a bad day.
3. Lay out nodes — messages, delays, multi-way branching
Connect nodes in the order a visitor moves through them. For this flow:- Condition — source gate. Five comparison operators are available:
equals,not_equals,contains,greater_than,less_than. Here,field=source,operator=equals,value=signup_form. Connect theyesedge onward; thenoedge ends the run. - Send SMS — the welcome. One messaging node per channel,
toandbodyin the node’s settings.body=Welcome to Acme, {{first_name}}! We'll text you a getting-started tip tomorrow.See Flow Builder for WhatsApp, email, RCS, and the other channels. - Delay — the cadence. Amount
24, unithours. Long waits (over a minute) park the run — it showswaitingon the Executions API and resumes automatically at its scheduled time.
loop is not a recognized node type at runtime — an unrecognized type is skipped, and a skipped iterator on a contact list is the classic spray-and-pray bug. Batch work belongs to a Schedule-triggered flow or Campaign enrollment; per-contact iteration can wait on the roadmap.
4. AI routing — classify, branch, hand to the agent
The AI nodes turn the flow into a router, not a fixed sequence:- AI Classify. Set the candidate intents as a comma-separated list, e.g.
interested,question,complaint,other. The node classifiescontext.message/context.body, and it routes on the classified intent: an edge whosesourceHandle(or label) matches the intent name wins, withyesandnohandles as fallbacks. - Run Agent.
data.agent_id(oragentId) points at an AI agent you have provisioned. The contact’s message goes to that agent, and the flow continues down the default edge once the agent returns. - Webhook — human alert. The
complaintbranch POSTs the run context to your team’s endpoint — the executor sends a body withexecution_idand the sanitized context. Public URLs only: the node validates the URL and rejects private/internal addresses, and only the approved HTTP methods pass.
error if you draw one, and falls back to the default next node only when you did not. An HTTP Request to a flaky endpoint without an error edge is a run that finishes on the default path with a hidden failure; route it on error to a fallback (often an email to ops) or end the run cleanly.
5. Variables and templating
Variables are flat keys. In every text field,{{first_name}} resolves against the merged context of the trigger payload, the contact record, and values earlier nodes wrote — a missing key renders as an empty string. Dotted paths like {{contact.first_name}} never resolve; write flat placeholders.
In the builder you capture values from an upstream node’s output (for example an HTTP Request response) and reference them downstream with {{...}}. In API-defined flows a transform (Functions) node derives values explicitly, using an assignments array — string template, a safe boolean expression, or a literal value — written into top-level keys:
6. Simulate before you publish
Two simulators cover two different questions: Test mode (canvas). Click Test in the builder toolbar to walk a flow with sample data. The builder highlights each node as it executes, so you can watch the exact branch the run takes before a real contact goes through it. Journey simulator (API). Before a launch, dry-run the flow against a real audience — no contact is enrolled, nothing is sent:POST /campaigns/journeys/simulate. Run it before every launch; a 50,000-contact typo discovered here is cheap, in production it is not.
7. Publish, then watch the first runs
Publish the flow — the API handles arePOST /flows/:id/publish and POST /flows/:id/unpublish.
Once live, debug with the Executions API. List failed runs for your flow:
input and output, and the error message, keyed by node_id and node_type so you can match it to the definition. The full field reference, status table, and error codes live in Flow Executions.
Work through a failed run the same way every time:
- Read
erroron the list row — the executor surfaces the last failed node and its reason. - Fetch the run and walk the
stepsarray in execution order. - At the failed step, expand the rendered
input. A placeholder like{{phone}}that resolves to a nulltofield, or acomplaintedge that points at no target, shows up here. - Compare
steps_completedtototal_stepsto see where the run stopped. - Subscribe to
flow.execution.failedover webhook events so failures page you instead of hiding in the list.
8. Iterate — version history, A/B tests, dashboards
Version history. Every save creates a server-side snapshot.GET /flows/:id/versions lists them, POST /flows/:id/versions takes a manual snapshot, and POST /flows/:id/versions/:versionId/restore rolls back onto the draft. Restoring on a published flow overwrites the draft, never live traffic — the live publish stays active until you re-publish the restored draft. In the builder, compare and roll back from Flow Settings > Version History.
A/B test nodes. An abTest node routes contacts down weighted variants and reports a per-variant winner. Validation requires at least two variants, and explicit weight_pct values must sum to 100 (an all-absent weight set splits evenly):
handleId) or an explicit downstream path_node_id; an optional bandit mode adapts weights from the flow’s live per-variant results instead of the static split.
Analytics. The Flows dashboard consumes the same funnel the simulator blends in, so the assumed drop-off converges on real history as runs accumulate.
Appendix — the worked example as an API definition
Create the flow through the Flows API with everything wired in one JSON definition — nodes, edges, and handles match what the builder would draw:no branch of the gate and the other intent simply stop — a condition or classify edge with no target ends the run cleanly. The notify_ops and end ids in the error-handling edges are placeholders for whatever you hang off the error path; the executor routes a failed node down the edge marked error before falling back to the default next node.
Recap checklist
- Design the narrative first; decompose into a linear path with one branch where possible.
- Pick one trigger and gate it — especially
contact.created, which fires on imports. - Chain Condition forks for multi-way logic; do not expect a Loop node (
loopis unrecognized and skipped). - Intent-edge routing on AI Classify; error edge on every fallible node.
- Flat
{{...}}keys everywhere; derive values with a Functions (transform) node in API-defined flows. - Simulate with Test mode and
POST /flows/:id/simulatebefore you go live. - Publish, watch the Executions API, debug by walking the step trace.
- Iterate with version snapshots and A/B nodes.
Next steps
- Flows overview — trigger and node-type reference
- Flow Builder — the canvas and every node
- Flow Executions — the full Executions API field reference