Sequences
| Method | Path | Scope | SDK |
|---|---|---|---|
POST | /api/v1/sequences | sequences:write | sequences.create |
GET | /api/v1/sequences | sequences:write | sequences.list |
GET | /api/v1/sequences/:key | sequences:write | sequences.get |
PUT | /api/v1/sequences/:key | sequences:write | sequences.update |
DELETE | /api/v1/sequences/:key | sequences:write | sequences.remove |
POST | /api/v1/sequences/:key/simulate | sequences:write | sequences.simulate |
POST | /api/v1/sequences/:key/enroll | sequences:write | sequences.enroll |
POST | /api/v1/sequences/:key/enroll/bulk | sequences:write | sequences.bulkEnroll |
Sequence reads also require sequences:write — there is no separate read scope.
:key is your own slug, URL-encoded.
POST /api/v1/sequences
Creates a linear sequence from steps; Mailroom builds the flow graph.
| Field | Type | Notes |
|---|---|---|
key | string | Required. Unique per project. |
name | string | Required. Display name. |
steps | array | Required. { template, subject?, type?, delayMinutes? }, in order. |
status | draft | active | paused | archived | Only active sequences enroll and send. |
trigger | string | Event type that auto-enrolls (matched against events.track({ type })). |
brand | string | Brand slug. |
frequencyCap | object | { count, windowHours } — ceiling on emails per contact. |
201
{ "ok": true, "id": "s1a2…", "key": "onboarding", "steps": 3 }
422 — validation failure or a reason code such as a duplicate key or unknown template.
GET /api/v1/sequences
{
"ok": true,
"sequences": [
{ "id": "s1a2…", "key": "onboarding", "name": "Onboarding",
"status": "active", "trigger": "user.signed_up", "created_at": "2026-07-01T00:00:00.000Z" }
]
}
GET /api/v1/sequences/:key
Returns the full flow graph, spread flat into the envelope:
{
"ok": true,
"key": "onboarding",
"name": "Onboarding",
"status": "active",
"reentry": "once_active",
"cooldownHours": null,
"goal": null,
"frequencyCap": { "count": 3, "windowHours": 168 },
"triggers": [{ "kind": "event", "config": { "type": "user.signed_up" }, "enabled": true }],
"nodes": [
{ "id": "n0", "kind": "trigger" },
{ "id": "n1", "kind": "send_email", "config": { "template": "welcome" } },
{ "id": "n2", "kind": "wait_delay", "config": { "minutes": 1440 } }
],
"edges": [{ "from": "n0", "to": "n1" }, { "from": "n1", "to": "n2" }]
}
Node kinds: trigger, send_email, wait_delay, wait_until, branch, split,
update_contact, webhook, subflow, goal, exit. Edges out of a branch or
split carry branchKey.
404 not_found — unknown key.
PUT /api/v1/sequences/:key
Replaces the graph atomically — send the whole object, not a patch. The graph is
portable: GET from one project, PUT into another.
{ "ok": true, "key": "onboarding", "nodes": 6 }
422 invalid_graph — the message lists every problem found; nothing is applied.
404 not_found — unknown key.
DELETE /api/v1/sequences/:key
Deletes the sequence and its enrollments.
{ "ok": true, "deleted": true }
POST /api/v1/sequences/:key/simulate
Dry-runs one contact through the flow. Sends nothing, creates no enrollment.
| Field | Type | Notes |
|---|---|---|
contactId | uuid | Simulate a real contact. |
contact | object | Or a hypothetical one: { status?, source?, timezone?, tags?, attributes? }. |
event | object | { type, payload? } — the event that would have triggered it. |
{
"ok": true,
"path": [{ "nodeId": "n0", "kind": "trigger" }, { "nodeId": "n1", "kind": "send_email", "detail": "welcome" }],
"sends": ["welcome", "getting-started"],
"endedBy": "exit"
}
endedBy: exit (clean finish), goal (goal condition met), dead_end (a branch
leads nowhere — a bug in the graph), hop_guard (step ceiling hit, usually a loop).
404 — unknown key, or the graph has no entry node.
POST /api/v1/sequences/:key/enroll
Body: { "email": "ada@example.com" } or { "contactId": "9f0c…" } — one is
required.
{ "ok": true, "enrollmentId": "e5f6…" }
422 — neither field given, or an enroll_failed reason (sequence not active,
contact unsubscribed, already enrolled under once_* re-entry). 404 — unknown
contact or sequence.
POST /api/v1/sequences/:key/enroll/bulk
Body: { "contactIds": [...] } or { "emails": [...] }.
{ "ok": true, "requested": 5000, "enrolled": 4812, "skipped": 188 }
skipped counts contacts already active in the sequence. The SDK chunks inputs
above 5000 recipients into sequential requests and sums the totals; raw callers
should do the same.