Send a different email to different readers
A sequence that sends everybody the same three emails treats a reader who has supported you for two years exactly like one who subscribed on Tuesday and has not opened anything since. Commune can ask, part way through, what it knows about the person and what they have done with the email you already sent, and take a different path for each.
Before you start
A condition about a property the run cannot see matches every run, so the branch silently always takes the same arm. Commune refuses such a condition when you save, naming the path, which is why the first request here reads the trigger catalog's context rather than guessing at field names.
Your agent can read the sequence you already have, add the branch and send you the new emails to look at. This route writes, replacing the graph of a live automation, and an edit applies to the readers already part way through it. Ask it to tell you the graph back before it sends anything.
Connect once
Add Commune's MCP server to your agent, then approve the consent screen and pick your newsletter. Nothing else to install.
claude mcp add --transport http commune https://api.usecommune.com/mcpAsk
One prompt does the whole job. Paste it as it is, or change the parts that are yours.
Read my Commune newsletter's welcome sequence, then add a branch three days in:
- if the reader holds the Founding member tag, send them the members-only note instead of the usual second email, and end there; - otherwise, if they opened the first email, send the usual second email; - if they did not open it, send a short note asking them to check their spam folder.
Show me the graph before you save it, and send me a test copy of each new email.
What it uses
Your agent picks these of Commune's tools to answer. You do not name them; they are here so you know where every part of the answer comes from.
commune_readReads the trigger catalog (which properties a condition may name under this trigger), the newsletter's tags to find the one to branch on, and the automation with its whole graph including the Markdown of every email.
commune_writeSends the graph back with the branch in it, and sends test copies of the new emails. Replacing the graph of a live automation applies to the runs already in flight.
who_is_on_my_listAnswers how many readers hold the tag you are branching on, which is how you tell a branch worth building from one that will take the same arm every time.
What comes back
Here is the graph before I save it. Two conditions, three endings:
| Step | Asks | Then | Otherwise |
|---|---|---|---|
member | Holds the Founding member tag? | insider, and the run ends | read-it |
read-it | Opened welcome? | best-of, and the run ends | nudge |
- 214 readers hold Founding member, so the first arm is worth having rather than theoretical.
- The tag is matched by id, not by name, so renaming it in Commune will not break the branch.
- Both conditions carry `required`. Without it a missing property matches everything and the branch would always take the same side.
- Say the word and I will save it. Test copies of
insiderandnudgeare in your inbox.
An example. Yours comes from your own newsletter.
Worth asking next
- How many readers have taken each arm this month?
- Take the nudge back out: the readers who never opened should get nothing more.
- Which readers are sitting on the wait step right now?
Tips
- Ask what the condition can see before you describe the branch. A condition can read the event that started the run, the subscriber's record as it stands now, and whether an earlier email in this sequence was opened or clicked. It cannot read anything else, and a path it cannot see is refused when you save.
- A tag is read at the moment the condition runs, not when the run started. That is the whole value of branching three days in: somebody who upgraded on Tuesday takes the member arm on Thursday.
- An arm ends where its last step stops pointing anywhere, so you do not have to say "and then nothing". Commune used to fall through to the next step in order, which meant an arm that did not end ran into the following arm's email and the reader got both; that changed on 2026-10-14. There is no step type for ending a path either: a
stopstep existed for saying it out loud and was removed on 2026-10-15, because the arrow with nothing on the end of it already says it.
Four requests and one read. Pull the graph you have, find the tag to branch on, send the graph back with the condition in it, look at the new emails, and read one run to see which arm it took. A fifth request takes an arm away again, which is the edit that has to say what happens to the readers standing on it.
Before you start
- An API key with
sending: write,audience: read, exported asCOMMUNE_API_KEY. - A sequence that already exists. Welcome every new reader writes one; this page edits it.
- The
idof that automation. It is in the list of the newsletter's automations, and in the answer to the request that created it. - Some steps need more than this. Each one lists it where it starts.
Run it in your client
Every request below, in order, as a collection. Import the file into Postman, Insomnia, Bruno, Yaak or Hoppscotch, set apiKey, and send them one by one.
Every request sends Authorization: Bearer $COMMUNE_API_KEY and Commune-Version: 2026-08-26.
- 1
Read the graph you already have
One automation with its whole graph: every step, every condition, and the Markdown of every email it sends. Read it first for a practical reason rather than a tidy one:
stepson an update replaces the whole graph, so the request in step 3 has to carry the steps you are keeping as well as the ones you are adding.The
body_markdownthat comes back is the rendition you sent, with the personalization placeholders as you wrote them, so reading an automation, changing one step and sending the graph back preserves everything else. Resolving the placeholders here would silently replace every greeting in the sequence with its fallback the next time anybody saved it.Every field is present on every step,
nullwhere the type does not use it. That is the opposite of the request shape, which refuses a field a type does not use, and the asymmetry is deliberate: a reader wants one key set to narrow, and a writer wants to be told when it has said something meaningless. A body built by sending this back is accepted unchanged.cURLcurl "https://api.usecommune.com/automations/9c1e4b7a-2d35-4f61-8a90-1b2c3d4e5f60" \ -H "Authorization: Bearer $COMMUNE_API_KEY" \ -H "Commune-Version: 2026-08-26"Response 200{ "object": "automation", "id": "9c1e4b7a-2d35-4f61-8a90-1b2c3d4e5f60", "newsletter": { "object": "newsletter", "id": "7d3f1c02-58a1-4a4e-9a0b-2f6d1c9e4411" }, "name": "Welcome sequence", "status": "active", "trigger": "subscriber.created", "trigger_filter": null, "reentry": false, "steps": [ { "id": "welcome", "type": "send_email", "next": "settle", "then": null, "else": null, "duration": null, "tag": null, "schema": null, "email": { "subject": "Welcome, {{ subscriber.first_name }}", "preview_text": "Here is where to start.", "body_markdown": "# Glad you are here\n\nReply to this one and tell me what you are working on. I read all of them.\n\n[Unsubscribe]({{ unsubscribe_url }}) · {{ address }}\n" } }, { "id": "settle", "type": "wait", "next": "best-of", "then": null, "else": null, "duration": "P3D", "tag": null, "schema": null, "email": null }, { "id": "best-of", "type": "send_email", "next": null, "then": null, "else": null, "duration": null, "tag": null, "schema": null, "email": { "subject": "Three things worth reading", "preview_text": null, "body_markdown": "# Three things worth reading\n\nA few pieces that explain what this letter is about better than I can in a\nwelcome email.\n\n[Unsubscribe]({{ unsubscribe_url }}) · {{ address }}\n" } } ], "created_at": "2026-10-01T09:00:00Z", "updated_at": "2026-10-02T11:16:00Z", "activated_at": "2026-10-02T11:16:00Z" } - 2
Find the tag you want to branch on
A condition names a tag by
id, not by name, which is why this request exists: a name would be friendlier to write and would break the moment somebody renamed the tag in Commune.known_subscriber_countis the number worth looking at before you build the branch. A tag nobody holds is a branch that will take the same arm every time, which is exactly the failure a condition is hardest to notice.Retired tags are left out unless you ask for them. They still resolve the audience of articles sent before they were retired, but a step naming one is refused, because it would do nothing, every run, forever.
cURLcurl "https://api.usecommune.com/newsletters/example-letter/tags" \ -H "Authorization: Bearer $COMMUNE_API_KEY" \ -H "Commune-Version: 2026-08-26"Response 200{ "object": "list", "data": [ { "object": "tag", "id": "aa11bb22-cc33-4d44-8e55-ff6677889900", "newsletter": { "object": "newsletter", "id": "7d3f1c02-58a1-4a4e-9a0b-2f6d1c9e4411" }, "name": "Founding member", "known_subscriber_count": 214, "is_room": false, "retired": false, "retired_at": null, "created_at": "2025-06-11T08:45:00Z" } ], "pagination": { "has_more": false, "next_cursor": null } } - 3
Send the graph back with the branch in it
Two conditions, and three endings.
memberasks whether the reader holds the tag now, three days into the sequence rather than when they subscribed, which is the whole reason a condition is worth having.read-itasks whether the first email was opened, which Commune knows because everysend_emailstep in a run reportsopenedandclickedonce its delivery has been attributed.Both carry `required`, and that is not decoration. JSON Schema treats an absent property as saying nothing, so
propertiesalone asks "if this is present it has to look like this" and matches a run where it is missing.requiredis what asks for it to be there at all. A condition naming a path the trigger cannot produce is refused outright when you save, naming the path.Each arm ends by not pointing anywhere.
insider,best-ofandnudgeleavenextout, and that ends the run: there are three endings and no step saying so. This graph used to need two such steps, becausenextdefaulted to the following step in the array and without anend-insiderthe member arm fell straight intoread-itand a member was asked the engagement question as well. That defaulting was removed on2026-10-14, and thestoptype that was left over went on2026-10-15: an edge you leave out is the only way to end a path now, and it is the one that was always there.The whole graph is checked before anything is stored: every edge points at a step that exists, every path ends, there are no cycles, every condition is in the keyword subset and names paths this trigger can produce, and every email still carries its unsubscribe link, its mailing address and images that load. Any of those is a
422naming which.The edit applies to the runs already in flight. A run sits on a step
id, so a subscriber three days in picks up the new steps from wherever they are. That is the point of editing a live sequence rather than replacing it.cURLcurl -X PATCH "https://api.usecommune.com/automations/9c1e4b7a-2d35-4f61-8a90-1b2c3d4e5f60" \ -H "Authorization: Bearer $COMMUNE_API_KEY" \ -H "Commune-Version: 2026-08-26" \ -H "Idempotency-Key: welcome-branch-2026-10-08" \ -H "Content-Type: application/json" \ -d '{ "steps": [ { "id": "welcome", "type": "send_email", "next": "settle", "email": { "subject": "Welcome, {{ subscriber.first_name }}", "preview_text": "Here is where to start.", "body_markdown": "# Glad you are here\n\nReply to this one and tell me what you are working on. I read all of them.\n\n[Unsubscribe]({{ unsubscribe_url }}) · {{ address }}\n" } }, { "id": "settle", "type": "wait", "duration": "P3D", "next": "member" }, { "id": "member", "type": "condition", "schema": { "properties": { "subscriber": { "properties": { "tags": { "contains": { "const": "aa11bb22-cc33-4d44-8e55-ff6677889900" } } }, "required": [ "tags" ] } }, "required": [ "subscriber" ] }, "then": "insider", "else": "read-it" }, { "id": "insider", "type": "send_email", "email": { "subject": "The part I only tell members", "body_markdown": "# The part I only tell members\n\nYou have been supporting this letter from early on, so here is the thing I\nleave out of the public archive: what I am working on next, and why.\n\n[Unsubscribe]({{ unsubscribe_url }}) · {{ address }}\n" } }, { "id": "read-it", "type": "condition", "schema": { "properties": { "steps": { "properties": { "welcome": { "properties": { "opened": { "const": true } }, "required": [ "opened" ] } }, "required": [ "welcome" ] } }, "required": [ "steps" ] }, "then": "best-of", "else": "nudge" }, { "id": "best-of", "type": "send_email", "email": { "subject": "Three things worth reading", "body_markdown": "# Three things worth reading\n\nA few pieces that explain what this letter is about better than I can in a\nwelcome email.\n\n[Unsubscribe]({{ unsubscribe_url }}) · {{ address }}\n" } }, { "id": "nudge", "type": "send_email", "email": { "subject": "Did this land in the wrong folder?", "body_markdown": "# Did this land in the wrong folder?\n\nThe first email I sent you is still unread, which usually means it went\nsomewhere other than your inbox. If you want it, dragging one of these into\nyour main folder is normally enough.\n\n[Unsubscribe]({{ unsubscribe_url }}) · {{ address }}\n" } } ] }'Response 200{ "object": "automation", "id": "9c1e4b7a-2d35-4f61-8a90-1b2c3d4e5f60", "newsletter": { "object": "newsletter", "id": "7d3f1c02-58a1-4a4e-9a0b-2f6d1c9e4411" }, "name": "Welcome sequence", "status": "active", "trigger": "subscriber.created", "trigger_filter": null, "reentry": false, "steps": [ { "id": "welcome", "type": "send_email", "next": "settle", "then": null, "else": null, "duration": null, "tag": null, "schema": null, "email": { "subject": "Welcome, {{ subscriber.first_name }}", "preview_text": "Here is where to start.", "body_markdown": "# Glad you are here\n\nReply to this one and tell me what you are working on. I read all of them.\n\n[Unsubscribe]({{ unsubscribe_url }}) · {{ address }}\n" } }, { "id": "settle", "type": "wait", "next": "member", "then": null, "else": null, "duration": "P3D", "tag": null, "schema": null, "email": null }, { "id": "member", "type": "condition", "next": null, "then": "insider", "else": "read-it", "duration": null, "tag": null, "schema": { "properties": { "subscriber": { "properties": { "tags": { "contains": { "const": "aa11bb22-cc33-4d44-8e55-ff6677889900" } } }, "required": [ "tags" ] } }, "required": [ "subscriber" ] }, "email": null }, { "id": "insider", "type": "send_email", "next": null, "then": null, "else": null, "duration": null, "tag": null, "schema": null, "email": { "subject": "The part I only tell members", "preview_text": null, "body_markdown": "# The part I only tell members\n\nYou have been supporting this letter from early on, so here is the thing I\nleave out of the public archive: what I am working on next, and why.\n\n[Unsubscribe]({{ unsubscribe_url }}) · {{ address }}\n" } }, { "id": "read-it", "type": "condition", "next": null, "then": "best-of", "else": "nudge", "duration": null, "tag": null, "schema": { "properties": { "steps": { "properties": { "welcome": { "properties": { "opened": { "const": true } }, "required": [ "opened" ] } }, "required": [ "welcome" ] } }, "required": [ "steps" ] }, "email": null }, { "id": "best-of", "type": "send_email", "next": null, "then": null, "else": null, "duration": null, "tag": null, "schema": null, "email": { "subject": "Three things worth reading", "preview_text": null, "body_markdown": "# Three things worth reading\n\nA few pieces that explain what this letter is about better than I can in a\nwelcome email.\n\n[Unsubscribe]({{ unsubscribe_url }}) · {{ address }}\n" } }, { "id": "nudge", "type": "send_email", "next": null, "then": null, "else": null, "duration": null, "tag": null, "schema": null, "email": { "subject": "Did this land in the wrong folder?", "preview_text": null, "body_markdown": "# Did this land in the wrong folder?\n\nThe first email I sent you is still unread, which usually means it went\nsomewhere other than your inbox. If you want it, dragging one of these into\nyour main folder is normally enough.\n\n[Unsubscribe]({{ unsubscribe_url }}) · {{ address }}\n" } } ], "created_at": "2026-10-01T09:00:00Z", "updated_at": "2026-10-08T10:04:00Z", "activated_at": "2026-10-02T11:16:00Z" } - 4
Look at the emails the new arms send
You'll need
- An address you can read. It does not have to be a subscriber.
Two new bodies went in with that edit, and nobody has read either of them rendered. A test copy is the only way anybody sees an automation email before a subscriber does, because people arrive one at a time and there is no moment at which somebody watches the sequence go out.
It renders through the same renderer and the same personalization pass the real send uses, touches no run and nobody on the list, and takes up to five addresses. Run it again for
nudge.cURLcurl -X POST "https://api.usecommune.com/automations/9c1e4b7a-2d35-4f61-8a90-1b2c3d4e5f60/steps/insider/send-test" \ -H "Authorization: Bearer $COMMUNE_API_KEY" \ -H "Commune-Version: 2026-08-26" \ -H "Idempotency-Key: insider-test-2026-10-08" \ -H "Content-Type: application/json" \ -d '{ "to": [ "[email protected]" ] }'Response 200{ "object": "automation_step_test", "automation": { "object": "automation", "id": "9c1e4b7a-2d35-4f61-8a90-1b2c3d4e5f60" }, "step": "insider", "subject": "The part I only tell members", "recipients": [ "[email protected]" ], "sent_to_owner": false, "sent": 1, "failed": 0 } - 5
Check which arm a reader took
A run is the record of one subscriber's passage, and
stepsis the answer to "which way did this one go": one entry per step that reached the point of sending, so a step the run branched away from has none at all.The run below took the member arm.
welcomeandinsiderare there,best-ofandnudgeare not, andcurrent_stepis null becauseinsiderpointed nowhere and that finished it.statusiscompleted, which means it reached the end of its path rather than stopping short.This is also how you tell a branch that works from one that is always taking the same side. If every run in the list has the same
steps, the condition is matching the same way every time, which usually means it names a property the run cannot see or is missing itsrequired.cURLcurl "https://api.usecommune.com/runs/77889900-aabb-4ccd-8eef-112233445566" \ -H "Authorization: Bearer $COMMUNE_API_KEY" \ -H "Commune-Version: 2026-08-26"Response 200{ "object": "automation_run", "id": "77889900-aabb-4ccd-8eef-112233445566", "automation": { "object": "automation", "id": "9c1e4b7a-2d35-4f61-8a90-1b2c3d4e5f60" }, "subscriber": { "object": "subscriber", "id": "44556677-8899-4aa1-b2c3-d4e5f6071829" }, "status": "completed", "current_step": null, "exit_reason": null, "attempts": 3, "steps": [ { "step": "welcome", "status": "clicked", "failure_reason": null, "sent_at": "2026-10-05T08:00:01Z", "delivered_at": "2026-10-05T08:00:06Z", "opened_at": "2026-10-05T08:41:12Z", "clicked_at": "2026-10-05T08:42:03Z", "bounced_at": null, "complained_at": null }, { "step": "insider", "status": "delivered", "failure_reason": null, "sent_at": "2026-10-08T08:00:02Z", "delivered_at": "2026-10-08T08:00:11Z", "opened_at": null, "clicked_at": null, "bounced_at": null, "complained_at": null } ], "next_run_at": null, "created_at": "2026-10-05T08:00:00Z", "completed_at": "2026-10-08T08:00:02Z" } - 6
Take an arm away again, and say where its readers go
A month later you decide the readers who never opened anything should get nothing more rather than a note about their spam folder. So
nudgecomes out, andread-it's other arm points at nothing instead, which ends the run there.That edit is refused on its own. A run sits on a step
id, so removing a step people are standing on would leave those subscribers part way through the sequence with nothing to advance them and nothing reporting it. Withouton_removed_stepthe answer is a409saying how many runs are waiting and on which steps.on_removed_stepis either"exit", which ends those runs, or theidof a step in the graph you are sending, which is where they carry on from. A target that is not in the new graph is refused, because moving a run to a step that is also being removed would strand it again. It is ignored when the edit removes nothing anything is waiting on, and refused when the request sends nostepsat all, since then it removes nothing.The runs it ends get an
exit_reasonofstep_removed, and each publishesautomation.run.exited, so a consumer watching the sequence sees why rather than seeing a run go quiet.Pausing is the other way to stop an arm
If what you want is to stop the whole sequence for a while rather than change it, pause it. A pause holds every waiting run exactly where it is and resumes from that step when you turn it back on, so nobody loses their place and nobody is emailed twice.
cURLcurl -X PATCH "https://api.usecommune.com/automations/9c1e4b7a-2d35-4f61-8a90-1b2c3d4e5f60" \ -H "Authorization: Bearer $COMMUNE_API_KEY" \ -H "Commune-Version: 2026-08-26" \ -H "Idempotency-Key: welcome-drop-nudge-2026-11-04" \ -H "Content-Type: application/json" \ -d '{ "on_removed_step": "exit", "steps": [ { "id": "welcome", "type": "send_email", "next": "settle", "email": { "subject": "Welcome, {{ subscriber.first_name }}", "preview_text": "Here is where to start.", "body_markdown": "# Glad you are here\n\nReply to this one and tell me what you are working on. I read all of them.\n\n[Unsubscribe]({{ unsubscribe_url }}) · {{ address }}\n" } }, { "id": "settle", "type": "wait", "duration": "P3D", "next": "member" }, { "id": "member", "type": "condition", "schema": { "properties": { "subscriber": { "properties": { "tags": { "contains": { "const": "aa11bb22-cc33-4d44-8e55-ff6677889900" } } }, "required": [ "tags" ] } }, "required": [ "subscriber" ] }, "then": "insider", "else": "read-it" }, { "id": "insider", "type": "send_email", "email": { "subject": "The part I only tell members", "body_markdown": "# The part I only tell members\n\nYou have been supporting this letter from early on, so here is the thing I\nleave out of the public archive: what I am working on next, and why.\n\n[Unsubscribe]({{ unsubscribe_url }}) · {{ address }}\n" } }, { "id": "read-it", "type": "condition", "schema": { "properties": { "steps": { "properties": { "welcome": { "properties": { "opened": { "const": true } }, "required": [ "opened" ] } }, "required": [ "welcome" ] } }, "required": [ "steps" ] }, "then": "best-of", "else": null }, { "id": "best-of", "type": "send_email", "email": { "subject": "Three things worth reading", "body_markdown": "# Three things worth reading\n\nA few pieces that explain what this letter is about better than I can in a\nwelcome email.\n\n[Unsubscribe]({{ unsubscribe_url }}) · {{ address }}\n" } } ] }'Response 200{ "object": "automation", "id": "9c1e4b7a-2d35-4f61-8a90-1b2c3d4e5f60", "newsletter": { "object": "newsletter", "id": "7d3f1c02-58a1-4a4e-9a0b-2f6d1c9e4411" }, "name": "Welcome sequence", "status": "active", "trigger": "subscriber.created", "trigger_filter": null, "reentry": false, "steps": [ { "id": "welcome", "type": "send_email", "next": "settle", "then": null, "else": null, "duration": null, "tag": null, "schema": null, "email": { "subject": "Welcome, {{ subscriber.first_name }}", "preview_text": "Here is where to start.", "body_markdown": "# Glad you are here\n\nReply to this one and tell me what you are working on. I read all of them.\n\n[Unsubscribe]({{ unsubscribe_url }}) · {{ address }}\n" } }, { "id": "settle", "type": "wait", "next": "member", "then": null, "else": null, "duration": "P3D", "tag": null, "schema": null, "email": null }, { "id": "member", "type": "condition", "next": null, "then": "insider", "else": "read-it", "duration": null, "tag": null, "schema": { "properties": { "subscriber": { "properties": { "tags": { "contains": { "const": "aa11bb22-cc33-4d44-8e55-ff6677889900" } } }, "required": [ "tags" ] } }, "required": [ "subscriber" ] }, "email": null }, { "id": "insider", "type": "send_email", "next": null, "then": null, "else": null, "duration": null, "tag": null, "schema": null, "email": { "subject": "The part I only tell members", "preview_text": null, "body_markdown": "# The part I only tell members\n\nYou have been supporting this letter from early on, so here is the thing I\nleave out of the public archive: what I am working on next, and why.\n\n[Unsubscribe]({{ unsubscribe_url }}) · {{ address }}\n" } }, { "id": "read-it", "type": "condition", "next": null, "then": "best-of", "else": null, "duration": null, "tag": null, "schema": { "properties": { "steps": { "properties": { "welcome": { "properties": { "opened": { "const": true } }, "required": [ "opened" ] } }, "required": [ "welcome" ] } }, "required": [ "steps" ] }, "email": null }, { "id": "best-of", "type": "send_email", "next": null, "then": null, "else": null, "duration": null, "tag": null, "schema": null, "email": { "subject": "Three things worth reading", "preview_text": null, "body_markdown": "# Three things worth reading\n\nA few pieces that explain what this letter is about better than I can in a\nwelcome email.\n\n[Unsubscribe]({{ unsubscribe_url }}) · {{ address }}\n" } } ], "created_at": "2026-10-01T09:00:00Z", "updated_at": "2026-11-04T09:12:00Z", "activated_at": "2026-10-02T11:16:00Z" }
All together
The edit in step 3 as a read-modify-write, which is the shape every change to a live graph takes. It reads the graph, splices the branch in, and sends the whole thing back. The steps it is keeping are carried across rather than retyped, so a body you have not looked at in a month cannot be dropped by accident.
const BASE = "https://api.usecommune.com";
const VERSION = "2026-08-26";
const KEY = process.env.COMMUNE_API_KEY;
const AUTOMATION = process.env.COMMUNE_AUTOMATION;
const TAG = process.env.COMMUNE_TAG;
async function api(path, { method = "GET", body, idempotencyKey } = {}) {
const res = await fetch(BASE + path, {
method,
headers: {
authorization: `Bearer ${KEY}`,
"commune-version": VERSION,
...(body ? { "content-type": "application/json" } : {}),
...(idempotencyKey ? { "idempotency-key": idempotencyKey } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
const answer = await res.json();
if (!res.ok) {
const { code, message, request_id } = answer.error;
throw new Error(`${res.status} ${code}: ${message} (request ${request_id})`);
}
return answer;
}
// 1. The graph as it stands. `steps` on an update replaces it entirely, so
// everything being kept has to be carried across.
const before = await api(`/automations/${AUTOMATION}`);
const keep = (id) => before.steps.find((step) => step.id === id);
// 2. The branch. `required` is the half that is easy to leave out: an absent
// property matches everything in JSON Schema, so without it the condition
// would take the same arm on every run.
const holdsTheTag = {
properties: {
subscriber: {
properties: { tags: { contains: { const: TAG } } },
required: ["tags"],
},
},
required: ["subscriber"],
};
const openedTheFirst = {
properties: {
steps: {
properties: {
welcome: { properties: { opened: { const: true } }, required: ["opened"] },
},
required: ["welcome"],
},
},
required: ["steps"],
};
// 3. The whole graph back, with the conditions spliced in. Each arm ends by
// leaving `next` out, which ends the run: three endings, no node for any
// of them, because there is no step type that means "end here".
const steps = [
{ ...keep("welcome"), next: "settle" },
{ ...keep("settle"), next: "member" },
{ id: "member", type: "condition", schema: holdsTheTag, then: "insider", else: "read-it" },
{
id: "insider",
type: "send_email",
email: {
subject: "The part I only tell members",
body_markdown: process.env.INSIDER_BODY,
},
},
{ id: "read-it", type: "condition", schema: openedTheFirst, then: "best-of", else: "nudge" },
keep("best-of"),
{
id: "nudge",
type: "send_email",
email: {
subject: "Did this land in the wrong folder?",
body_markdown: process.env.NUDGE_BODY,
},
},
];
const after = await api(`/automations/${AUTOMATION}`, {
method: "PATCH",
idempotencyKey: "welcome-branch-v1",
body: { steps },
});
console.log(`${after.name}: ${after.steps.length} steps, status ${after.status}.`);
// 4. Nobody has read the new emails rendered. Do that before anybody arrives.
for (const id of ["insider", "nudge"]) {
const test = await api(`/automations/${after.id}/steps/${id}/send-test`, {
method: "POST",
idempotencyKey: `branch-test-${id}-v1`,
body: { to: [process.env.REVIEW_ADDRESS] },
});
console.log(`${id}: sent ${test.sent}, failed ${test.failed}`);
}Next use cases
See all- AgentAPI
Tag a reader the moment they act
Build the segment while people earn it, and say something to them while it still matters.
Read the use case - API
Bring the conversation to your site
Show each issue's discussion wherever your readers already are.
Read the use case - AgentAPI
Get a pulse on your newsletter in plain words
Ask how things are going and get an answer, not a dashboard.
Read the use case