Follow-ups that read the reply
A flow follows one contact over time and decides what to send next from what came back. It is stored as a list of nodes, edited on a canvas in the console, and driven by the same API you use for everything else.
Flow or campaign?
They are different shapes and the distinction decides which one you want. A campaign is one message to many contacts, once — you supply an audience and it works through it. A flow is many messages to one contact, over time — contacts enter one at a time as a trigger fires, and each has their own position in it.
| Campaign | Flow | |
|---|---|---|
| Starts | You send it, or a scheduled time arrives | An event fires for one contact |
| Audience | Resolved once, from a filter or a list | Never a list — people arrive individually |
| Shape | One message, with wording variants | A graph of nodes that branches |
| Ends | When the audience is exhausted | On a reply, or at a stop node |
Endpoints
sequences and the console calls them Flows. Same object — the endpoint path is the older name and stays stable.Triggers
What puts somebody into a flow. Only one per flow, because a contact arriving by two routes at once is an ambiguity nobody wants to debug.
| Trigger | Fires when |
|---|---|
keyword | An inbound message matches one of your keywords. |
new_contact | Somebody messages this number for the first time. |
tag_added | A tag is put on a contact, by you or by another flow. |
manual | You call the enrol endpoint yourself. |
Nodes
Steps are a JSON list. Each entry may carry an id, a type and a next; anything without them is read as a message that falls through to the entry after it — which is exactly what a flat drip already meant, so old sequences keep working untouched.
| Type | Does | Key fields |
|---|---|---|
| message | Sends a message. | body |
| wait | Pauses before the next node. | delay_hours |
| condition | Branches on a reply, a tag, or what the reply contained. | condition |
| tag | Adds a tag to the contact. | tag |
| stop | Ends the flow for this contact. | — |
| classify | Asks a model which category the reply falls into, then branches. | categories |
| agent | Lets an agent write the next message itself, to a goal you set. | goal |
{
"name": "Abandoned enquiry",
"session": "support",
"trigger": "keyword",
"trigger_keywords": ["price", "how much"],
"steps": [
{ "id": "ask", "type": "message",
"body": "Hi {{name}} — happy to help. What are you looking at?",
"next": "think" },
{ "id": "think", "type": "wait", "delay_hours": 4, "next": "sort" },
{ "id": "sort", "type": "classify",
"categories": {
"ready": "They want to buy now or are asking how to pay",
"browsing": "They are comparing options or asking about features",
"problem": "They are unhappy, confused, or reporting a fault"
},
"branches": { "ready": "close", "browsing": "nudge" },
"fallback": "human" },
{ "id": "close", "type": "agent",
"goal": "Give them the payment link and answer any question about it.",
"next": "done" },
{ "id": "nudge", "type": "message",
"body": "No rush — here is the comparison sheet.", "next": "done" },
{ "id": "human", "type": "tag", "tag": "needs-a-person", "next": "done" },
{ "id": "done", "type": "stop" }
]
}What the runner guarantees
Three behaviours are not configurable, because getting any of them wrong is worse than the feature not existing.
| Rule | Why |
|---|---|
| A reply stops the flow | On by default via stop_on_reply. A drip talking over a live conversation is worse than no drip. |
| AI failure routes, never strands | A model that is down, over budget, refusing, or answering with something unrecognised sends the contact to the fallback branch. No enrolment is ever left stuck. |
| Cycles are refused at save time | Validation walks the graph, so a loop is an error you see while editing rather than a contact receiving the same message forever. |
classify or agent node costs a model call per contact. A flow with a thousand people in it makes a thousand calls, and they draw on the same monthly AI budget as your inbound replies.