OpenSequence
DocsAPI referenceSDKs and CLIReference
Sequences

Nodes

Every node type with its params and transitions, one example each.

Edit on GitHub
typeParamsTransitionsRuns
startnonethen (required)inline
outreach email sendsubject, body, format (text or html), reply_in_thread, namesent, skipped, failedqueue seq_email
outreach sms sendbody, namesent, skipped, failedqueue seq_sms
waitdurationthen (required)inline timer
wait_forevents[], timeoutone per event, timeouttimer and signal
conditionmode, rules[]yes, no (both required)inline
taskkind, title, body, link, due, expire, connected_outcomes, dialer, auto_completecompleted, no_answer, plus branches_by_outcomeinline, then waits
endlabelnoneinline

Exact schemas, defaults and limits are generated from the registries: Nodes and channels. An unwired transition that is taken ends the enrollment as completed.

start

Optional. A no-op that makes the entry obvious on a canvas.

"start": { "type": "start", "next": { "then": "email_intro" } }

outreach email

"email_intro": {
  "type": "outreach", "channel": "email", "action": "send",
  "params": {
    "name": "Intro",
    "subject": "Quick question, {{ contact.first_name | \"there\" }}",
    "body": "<p>Hi {{ contact.first_name | \"there\" }},</p><p>…</p><p><a href=\"{{ unsubscribe_url }}\">Unsubscribe</a></p>",
    "format": "html",
    "reply_in_thread": false
  },
  "next": { "sent": "wait_2d", "skipped": "wait_2d", "failed": "end_failed" }
}

Sends through the sender's Gmail with the service's own Message-ID. format: "html" sends the body as HTML with a text alternative and HTML-escapes every rendered value; text (default) sends plain text. reply_in_thread: true replies in the thread of the previous email of this enrollment (In-Reply-To and the same Gmail thread), which is how a follow-up shows under the first message. A body without {{ unsubscribe_url }} publishes with the warning NO_UNSUBSCRIBE.

sent is taken after the provider accepted the message; skipped after a gate refused it (no email, suppressed, channel missing or not active, country, and so on; skipped falls back to sent when unwired); failed after the retries ran out (five reads of the queue message), and when failed is unwired the enrollment pauses with step_failed. Caps and windows defer rather than skip. Limits: subject 300, body 100,000 characters.

outreach sms

"sms_follow": {
  "type": "outreach", "channel": "sms", "action": "send",
  "params": { "body": "Hi {{ contact.first_name | \"there\" }}, this is {{ sender.first_name }} from Acme. Did you see my email? Reply STOP to opt out." },
  "next": { "sent": "wait_1d", "skipped": "wait_1d" }
}

At most one SMS send per sequence (TOO_MANY_SMS). Gates that skip: no_consent, no_phone, country_not_allowed, no_timezone, suppressed, no_sms_channel; the quiet-hours gate (08:00 to 21:00 recipient-local) defers. Over 320 characters the validator warns (TOO_LONG, it is more than two segments); the hard limit is 1000. An unknown send outcome is never retried: the run goes needs_review.

wait

"wait_2d": { "type": "wait", "params": { "duration": "2bd" }, "next": { "then": "check_stage" } }

A timer. bd counts business days in the contact's zone (the sender's when the contact has none), ending at the same time of day; the next outreach step is then pushed into the send window.

wait_for

"wait_reply": {
  "type": "wait_for",
  "params": { "events": ["reply:email", "meeting_booked"], "timeout": "5bd" },
  "next": { "reply:email": "end_replied", "meeting_booked": "end_booked", "timeout": "call_1" }
}

The only event wait. Events: reply, reply:email, reply:sms, reply:linkedin, connection_accepted, call_connected, meeting_booked. Each waited event needs a transition (BAD_EVENT); timeout is optional, and without one the enrollment waits forever (or until something exits it). A reply on any channel matches reply and reply:<channel>; a connected call matches call_connected and reply. Details in Wait for.

condition

"check_stage": {
  "type": "condition",
  "params": { "mode": "any", "rules": [
    { "field": "history.replied_since_enrollment", "op": "is_true" },
    { "field": "crm.deal.stage", "op": "is", "value": "Proposal" } ] },
  "next": { "yes": "end_done", "no": "sms_follow" }
}

all or any over 1 to 20 rules; yes and no both required. Evaluation is lazy (stops at the first decisive rule) and every value read is recorded on the run. Missing data never matches. Fields and operators: Conditions.

task

"call_1": {
  "type": "task",
  "params": { "kind": "call", "title": "Call {{ contact.first_name | \"them\" }} about {{ attrs.company.name | \"their company\" }}", "body": "Last email: {{ sequence.name }}", "due": "1bd", "expire": "3bd", "dialer": true },
  "next": { "completed": "wait_3d", "no_answer": "email_2" },
  "branches_by_outcome": { "connected": "end_connected", "voicemail": "sms_follow" }
}

Creates a task for the sender and waits. Completing it with an outcome takes branches_by_outcome[outcome], else completed; expiry takes no_answer. Everything about kinds, outcomes and the dialer: The task node.

end

"end_connected": { "type": "end", "params": { "label": "Connected" } }

Ends the enrollment as completed. The label shows in the journey and in your canvas; several end nodes make the per-step analytics tell the paths apart.

Which node types count as touches

Only outreach sends (email send, sms send) are touches: they increment the enrollment's touch_count, set last_touch_at and are the denominators of the delivered rates. A task is not a touch; a connected call is credited to the task's run as a reply.

On this page