Skip to main content

Node Reference

Every node type accepts a config object that's specific to its purpose. Common fields like id, type, and position (for the canvas) are stripped before execution.

Common fields

Every node — regardless of type — also carries:

FieldTypeNotes
display_namestringHuman-readable label shown on the canvas and in the jump-to-node edge picker. Case-insensitive unique within a flow; the validator rejects publishes with duplicate display names. Defaults to a type-derived placeholder ("Send 1", "Branch 2", etc.) until the user edits it.

The display name is what you pick from when wiring jump-to-node edges (a non-linear routing edge that targets a named node anywhere in the flow), so giving nodes meaningful names makes complex flows much easier to maintain.

note

"Copy node ID" was removed from the node context menu in May 2026 — referencing nodes by display_name is now the supported pattern for both UI wiring and operator readability.

Send

Sends a message to the contact across one or more channels with channel-specific content.

FieldTypeNotes
channel_chainstring[]Ordered list of channels to try. ["whatsapp", "telegram", "email"] means try WhatsApp first; if the contact has no WhatsApp method or it fails, fall back to Telegram, then email.
channel_contentobjectPer-channel payloads. Each key is a channel type; each value is the message body for that channel.
body_textstringLegacy / fallback. Used if channel_content is empty.
subjectstringEmail only. Required for email sends.
email_mailbox_iduuidEmail only. Picks the From mailbox.
template_iduuidWhatsApp only. References an APPROVED WhatsApp template.
template_variablesobjectWhatsApp template variable values, including variable_fallbacks for nullable fields.
header_image_urlstringWhatsApp template header image. Pre-uploaded to Meta as a 30-day media ID.
include_signaturebooleanEmail only. Default true. Appends the user's HTML signature.

Variable substitution. The body, subject, and template parameters all support {{contact.first_name}}, {{contact.email}}, {{vars.<flow_var>}}, {{flow.var.<name>}}, and {{flow.stop_url}}. Unresolved variables fall back to per-parameter variable_fallbacks (WhatsApp templates) or empty strings.

Conversation behavior. By default, flow sends do NOT open a conversation in the inbox. The first inbound reply attaches the prior 30 days of orphan flow messages to a new conversation (configurable per flow via inbound_behavior).

Delay

Pauses the run for a fixed amount of time.

FieldTypeNotes
durationintegerWait duration, in the chosen unit.
unit"seconds" | "minutes" | "hours" | "days"Time unit. The UI surfaces all four; the engine normalizes to seconds internally.
duration_secondsintegerRead-only effective value, computed from duration × unit. Max 14 days (1209600 seconds).

Standalone Delay node cap. A standalone Delay node is capped at 30 seconds. For longer waits, use a Condition node with wait_max_seconds (which is purpose-built for long-running waits and uses the tiered recheck cadence) — a standalone Delay tying up engine resources for hours or days would push backpressure onto the scheduler.

Inter-message delay inside Send. When a Send node carries 2 or more messages, the per-message delay is capped at 10 seconds (_SEND_MESSAGE_DELAY_MAX_MULTI = 10 on the server) to avoid tripping provider throttling on WhatsApp / Telegram bursts. The validator enforces this at save time; values above 10 s are rejected with an explanatory error.

Internally, the engine creates a scheduled_sends-style row with next_run_at = now + duration. The scheduler tick republishes a resume task when the deadline passes.

Collect Input

Sends a prompt and waits for the contact's next inbound message. The reply is stored in a flow variable.

FieldTypeNotes
promptstringMessage text shown to the contact. Same variable substitution as Send.
output_var_namestringFlow variable to write the reply into. Reference downstream as {{vars.<name>}}.
timeout_secondsintegerOptional. If no reply arrives within this window, the engine takes the timeout branch (configured via an outgoing edge with branch: "timeout").
channel_chainstring[]Same as Send — chooses where to send the prompt.

While a Collect Input node is waiting, replies on the same channel are not routed to a conversation in the inbox — they go to the flow. This makes flows usable as bot builders. Replies received during a Collect Input wait are still persisted as messages rows.

Condition (Branch)

Branches the flow based on a typed rule set. Optionally waits up to wait_max_seconds for the condition to become true (e.g., wait for a click, then take the YES branch).

FieldTypeNotes
rulesRule[]Each rule has field, operator, value. See operator list below.
rules_op"all" | "any"How rules combine. Default all.
wait_max_secondsintegerOptional. 0 (default) = evaluate once. >0 = re-evaluate periodically; if true, take YES; if still false at the deadline, take NO.

Edges from a Condition node carry branch: "yes" or branch: "no". Both must be present for the flow to publish.

Typed value picker. The value side of a rule can be picked from a typed dropdown sourced from GET /api/v1/flows/variables?flow_id={uuid}. The dropdown groups options into:

  • Contact fields (built-in: first_name, email, language, …)
  • Custom fields (your tenant's contact custom fields)
  • Tags
  • Lists
  • Flow variables (any collect_input, kb_query, or create_tracked_link output declared upstream in this flow)

Free-text values are still accepted — the picker is a convenience layer on top, not a constraint. See the variables endpoint for the underlying schema.

Operators:

OperatorApplies toDescription
equals / not_equalsany fieldExact string match.
contains / not_containsstringsSubstring match.
is_empty / is_not_emptyany fieldNULL / empty-string check.
in_list / not_in_listlist-typed fieldsTag membership, list membership.
link_clickedflow.var.<tracked_link_id>True once tracked_links.click_count > 0. Excludes bot/preview crawlers — see Tracked links.
language_in / language_not_incontact.language + contact.languagesMatches primary language OR auto-detected language list.

Tiered recheck cadence. When wait_max_seconds > 0, the engine re-evaluates the condition on a tiered schedule: every 10s for the first 10 minutes, every 30s for the next 2 hours, every 60s for the next 24 hours, then every 5 min until the deadline. This keeps fast feedback for click-tracking while remaining cheap for long waits.

Deadline anchoring. wait_max_seconds is anchored on the FIRST evaluation, not on each recheck. A 10-minute timeout fires 10 minutes after the condition node first ran, regardless of how often it has been rechecked.

Tag / Update Contact

Mutates the contact record without sending a message.

FieldTypeNotes
add_tagsstring[]Tag names to add. Created if they don't exist.
remove_tagsstring[]Tag names to remove.
set_fieldsobjectMap of contact-field name → value. Supports built-in fields (first_name, last_name, email, phone, etc.) and custom fields.
subscription_changesobject[]Each entry: { "channel_type": "whatsapp", "status": "subscribed" | "unsubscribed" }. Routes through SubscriptionService (audit-logged).

Open Conversation

Creates (or reuses) a real conversation row for the contact on a chosen channel. The conversation appears in the inbox and follows your normal assignment rules.

FieldTypeNotes
channel_typestringChannel to open the conversation on. Must match a channel the contact has a method for.
internal_notestringOptional. Posted as a tenant-internal note on the new conversation. Variable substitution supported.
assign_user_iduuidOptional. Direct-assigns the conversation to a team member.
assign_teamstringOptional. Routes to a team's queue.

Mostly used for "send 3 messages → if no click, open a conversation so an agent can intervene".

Generates a short URL (https://c.sendseven.com/<code>) that redirects to a target URL and counts clicks. The short URL is written into a flow variable for use in downstream Send nodes.

FieldTypeNotes
target_urlstringWhere the short URL redirects to. Variable substitution supported.
output_var_namestringFlow variable to write the short URL into. Default tracked_link_url.

The redirect handler filters bot/preview User-Agents (WhatsApp, Facebook, Telegram, Slack, iMessage, Discord, generic crawlers, headless browsers). Bot fetches go to tracked_links.preview_count; only real-human GETs increment click_count. This means the Condition link_clicked operator stays false until a human clicks — link-preview crawls don't accidentally fire YES.

Webhook

Calls an arbitrary HTTPS endpoint and continues based on the response.

FieldTypeNotes
urlstringHTTPS URL. HTTP allowed only for localhost / *.local. Private-IP literals are rejected.
method"GET" | "POST" | "PUT" | "PATCH" | "DELETE"Default POST.
headersobjectStatic request headers.
bodyobject | stringRequest body. Variable substitution supported.
timeout_secondsintegerDefault 10. Max 30.
success_codesinteger[]HTTP codes that count as success. Default [200, 201, 202, 204].

Edges can branch on success or error — the engine inspects the response status and takes the matching branch.

Run AI Assistant

Hands the conversation off to an AI Assistant (a Knowledge-Base-backed bot) for a bounded conversational sub-loop. The bot answers the contact's messages until one of three exit conditions fires, then the flow resumes on the matching branch.

FieldTypeNotes
bot_iduuidThe Assistant to run. Must be active.
max_turnsinteger | nullMaximum number of user→bot exchanges before the completed branch fires. null = unlimited (use with care). Valid range: 1..200.
allow_stop_keywordbooleanIf true, the contact can type a stop keyword (e.g., "human", "agent") to short-circuit the loop and take the escalated branch. Uses the bot's configured escalation keywords.
timeout_secondsintegerIf no contact reply arrives within this window, the timeout branch fires. No upper cap on the server side, but keep it within the channel's session window (24h for WhatsApp) to avoid template-send requirements on resume.
escalation_thresholdfloatConfidence below which the bot escalates. Persisted as 0.0..1.0; the UI surfaces it as a 0..100 % slider.

Required branches. The node must wire three outgoing edges:

BranchFires when
completedThe bot answered the user's question, hit max_turns, or the bot itself decided it was done.
timeoutNo user reply within timeout_seconds.
escalatedThe bot's confidence dropped below escalation_threshold, or the contact used a stop keyword (when allow_stop_keyword is on).

Missing branches surface as publish-time warnings — the flow can be saved without all three wired, but POST /publish will warn about the unrouted exits. Adding all three is required for predictable production behavior.

warning

Current limitation: the bot integration service does not yet count turns. Setting max_turns <= 1 is treated as "cap reached on turn 1" — the loop exits via completed after the first reply. If you need a strict one-shot bot response, this is the right setting; if you intended multi-turn behavior, set max_turns >= 2 once turn counting lands.

Stop / Terminate

Explicitly ends the run. Useful for "if condition X, stop here" branches that should not fall through to the rest of the flow.

FieldTypeNotes
reasonstringOptional. Stored on flow_runs.stop_reason for analytics.