SEQUENCE
What actually happens when someone clicks Pay
One request or job, end to end, in the order it happens. Who calls whom, what they send, who waits for an answer and who fires and forgets. The branches are drawn where they split, so the failure path sits next to the happy one instead of in someone's head.
An offline batch, end to end
A courier's handheld uploads a batch of scans it took offline. Each scan is written once, published to Kafka, applied by Dispatch, and a delivery ends in an SMS. From the Relay showcase.
Click a message for what is sent and where it is in the code. Click a participant to light up everything it sends and receives.
What it draws
Every field in the spec, and where it shows up.
PARTICIPANTS
- kind
- Drawn left to right in the order you list them, each with its own shape: a pill for a person, a cylinder for a database.personservicedatabasequeueexternal
- id · label
- Reuse the C4 element ID, so the same service has one name in every diagram.
- technology
- The second line in the box:
Go,Kafka,PostgreSQL 15.
MESSAGES
- kind
syncwaits for an answer: a filled arrow, and an activation bar on the receiver until it replies.asyncis fire and forget: an open arrow.replyis the answer: dashed, and it closes the bar.syncasyncreply- label · detail
- What is sent,
POST /v1/scans, with a smaller second line for the payload or the condition. Steps are numbered for you. - failure
- An error path, drawn red:
402 Payment Required. - from = to
- A call to itself, drawn as a loop on its own lifeline.
FRAGMENTS AND NOTES
- fragment
- A frame around the steps it holds, labelled with its condition.
altandpartakeelsebranches, and each branch starts from the calls that were in flight when the frame began. Frames nest.altoptloopparbreak - note
- A sticky note beside one participant, or across two.
Where it reads from
A flow lives in the code that runs it, so the skill picks one entry point and follows it:
The controller, resolver or consumer that receives the first message.
Service methods, HTTP and gRPC clients, and repository writes, in the order the handler makes them.
publish, emit and queue sends, drawn async, then the consumers that pick them up.
if / else around calls become alt. Loops over a batch become loop. Each message keeps a source link.
One flow per diagram. A sequence shows one path through the system, not all of them. For which services call which at all, use the System view; for the states a record moves through, use a lifecycle.
The spec you keep
{
"schema_version": 1,
"diagram_type": "sequence",
"meta": { "title": "Checkout", "trigger": "The buyer presses Pay" },
"participants": [
{ "id": "buyer", "label": "Buyer", "kind": "person" },
{ "id": "orders", "label": "Order API" },
{ "id": "stripe", "label": "Stripe", "kind": "external" }
],
"steps": [
{ "from": "buyer", "to": "orders", "label": "POST /orders" },
{ "from": "orders", "to": "stripe", "label": "create PaymentIntent" },
{ "fragment": "alt", "label": "card accepted",
"steps": [
{ "from": "orders", "to": "buyer", "label": "201 Created",
"kind": "reply" }
],
"else": [{ "label": "card declined", "steps": [
{ "from": "orders", "to": "buyer", "label": "402",
"kind": "reply", "failure": true }
] }]
}
]
}Name the flow by what starts it, in meta.trigger: “the buyer presses Pay”. List participants in the order the call travels.
Steps read top to bottom, exactly as they happen. Put a branch in an alt rather than a second diagram, so the declined card is drawn next to the accepted one.
The full contract is schemas/sequence.schema.json.
What validate flags
Errors
Exit 1. Each one names the step.
dangling-refa message or note naming a participant that is not listedtypea step that is not a message, a note or a fragmentduplicate-idtwo participants or messages sharing an ID
Warnings
Printed, never blocking.
labela message that does not say what is sentunuseda participant that sends and receives nothingelseelse branches on a fragment that cannot have themfragment-labela frame with no conditionsizemore than 40 messages or 10 participants. Split at a hand-off
The sentences it writes
A docs section can ask for sequence.messages: one sentence per step, carrying the frame it sits in, because “only when” is the part of a flow people get wrong. From the Relay build:
Step 5: Scan ingest notifies Parcel events without waiting. Message: parcel.scanned. Repeated: for each scan in the batch. Only when: a scan we have not seen.sequence.messages · scan-upload.sequence
Step 3: Scan ingest calls Relay DB and waits for the answer. Message: insert if new. Detail: unique index on idempotency_key. Repeated: for each scan in the batch.sequence.messages · scan-upload.sequence
What it connects to
The same boxes, in order
C4 shows that two containers talk. A sequence shows when, and what they say. Share the IDs and they read as one system.
C4 →Paste it into a README
Every render writes a .mmd beside the HTML: a Mermaid sequenceDiagram with numbering, branches and notes, for GitHub, GitLab and Notion.
Flows, next to the rest
Each sequence is an entry under Flows, beside the tables it writes and the routes it calls.
Dashboard →