Flows
Flows are SendSeven's visual automation engine. You design a journey on a canvas — a graph of nodes connected by edges — and the engine executes it for each contact you start the flow on. A single flow can send a message, wait for a reply, branch on conditions, update tags, hand off to a human agent, call a webhook, or chain into another flow.
Flows are useful for things like:
- Onboarding sequences — welcome message, wait 1 day, send a tutorial link, branch on whether the link was clicked.
- Drip campaigns — send 5 educational emails over 2 weeks with branching based on engagement.
- Bot-style conversations — collect a contact's name and preferences via Telegram/WhatsApp, then route them to the right team.
- Reactivation flows — for contacts who haven't replied in 30 days, send a discount code and tag the engaged ones.
- Birthday / anniversary greetings — use the
scheduletrigger, which supports cron-style and birthday-anchored rules.
Flows execute in the same multi-channel pipeline as the rest of the platform. Sends produce real messages rows, count toward usage-based billing, fire webhooks, and are visible in conversations once a contact replies.
Required scopes
| Scope | Purpose |
|---|---|
flows:read | List flows, view definition, view runs and run steps |
flows:write | Create, update, archive flows; start runs from the API |
flows:publish | Move a flow from draft to published |
Flow lifecycle
| Status | Description |
|---|---|
draft | Editable. Cannot start runs from this status. |
published | Active. New runs can be started; in-flight runs continue executing. |
paused | New runs are blocked. In-flight runs continue running until they finish or are stopped. |
archived | Hidden from default lists. Cannot start runs. |
A flow's definition (nodes + edges + trigger config) is mutable on draft and published flows. In-flight runs use a snapshot of the definition taken when they started, so editing a published flow does not break contacts who are already partway through it. Only new runs see the latest version.
Run lifecycle
| Status | Description |
|---|---|
running | Engine is actively executing nodes for this contact. |
waiting | Run is parked on a delay, a wait-for-reply, a wait-for-click, or a condition recheck. |
completed | Run reached a terminal state (no outgoing edges from the last node). |
failed | A node returned an unrecoverable error. The full traceback is stored on flow_runs.error_text. |
canceled | Stopped manually via API, the stop-URL token, or the STOP keyword. |
Anatomy of a flow
A flow definition is a JSON document with three top-level keys:
{
"schema_version": 1,
"trigger": {
"kind": "manual",
"event_name": null
},
"entry_node_id": "node_welcome",
"nodes": [
{ "id": "node_welcome", "type": "send", "config": { ... } },
{ "id": "node_wait", "type": "delay", "config": { "duration_seconds": 60 } },
{ "id": "node_followup", "type": "send", "config": { ... } }
],
"edges": [
{ "from": "trigger", "to": "node_welcome", "branch": "default" },
{ "from": "node_welcome", "to": "node_wait", "branch": "default" },
{ "from": "node_wait", "to": "node_followup", "branch": "default" }
]
}
Trigger: how runs are started. See Triggers and the API.
Nodes: the work each step does. See Node reference.
Edges: directed connections between nodes. The branch key specifies which branch of a conditional node the edge belongs to (default, yes, no, or a named branch).
Creating a flow
In the app, Flows → New flow opens a four-step wizard:
- Start — begin with a blank canvas or one of the built-in templates.
- Basics — name the flow.
- Channels — pick the flow's default channels (only channels you have connected are offered).
- Trigger — select and configure what starts the flow (see Triggers and the API).
Via the API, the same happens in one call — POST /api/v1/flows with name, definition, and optionally default_channel_chain and default_language.
Default channel chain
Every flow carries a flow-level default_channel_chain — an ordered list of channel types (e.g. ["whatsapp", "telegram", "sms"]) that every Send, Ask-with-Buttons, and Open Conversation node falls back to when it has no chain of its own. The engine walks the chain and delivers on the first channel the contact is reachable on (see Channel persistence).
Choose your channels deliberately. Each channel supports a different feature set (buttons, media, templates, message length). A flow that targets many channels is limited to the intersection of their capabilities, so only include channels you definitely want to support — fewer channels means more rich features are available everywhere.
API behavior on POST /flows:
emailis never a valid default-chain entry and is silently stripped — email sends are configured per-node.- If you omit
default_channel_chainand the definition contains interactive nodes, the chain is auto-seeded with all non-email channel types. - If you supply a chain that is empty (or email-only) for a flow with interactive nodes, the request is rejected with
400 FLOW_CHANNEL_CHAIN_REQUIRED.
Re-entry policy
When a flow is triggered for a contact who already has an active run, the engine consults the flow's re_entry_policy:
| Policy | Behavior |
|---|---|
block (default) | The new trigger is silently ignored. The existing run continues. |
restart | The existing run is canceled. A fresh run starts from the beginning. |
parallel | A second run is started alongside the existing one. Use sparingly — the engine doesn't deduplicate side effects across parallel runs. |
How a run advances
For each contact, the engine processes one node at a time:
- Load the run, the snapshotted definition, and the contact.
- Execute the current node. The node resolves to one of:
- continue — advance to the next node immediately.
- wait — pause on a timer, reply, click, or condition recheck.
- terminal — mark the run
completed. - error — mark the run
failedand emitflow.run.failed.
- Record an audit entry for the step.
- If
continue, loop back to step 1 with the next node. Ifwait, the run pauses and resumes automatically when the wait fires.
Each step runs exactly once and every side effect is idempotent, so a run can never double-execute a node even if processing is retried.
Webhooks
Flows emit three webhook events through the standard webhook bus:
flow.run.startedflow.run.completedflow.run.failed
See Webhooks for the full event schema.