
Twelve people share one account. The person who trips the event is rarely the person who should hear about it. And the thing you're trying to move is the account, not whoever happened to click.
Consumer lifecycle messages don't have that problem, so most journey flows aren't built for it. You end up with a sequence that nudges the intern who poked at a setting while the admin holding the renewal never hears a word.
Journeys is Courier's journey builder for multi-step customer messages. You build on a canvas: triggers, branching, delays, live data fetching, AI classification, omnichannel sends. Your engineering team (or your coding agent) wires up the event source once, and you own the logic, timing, content, and iteration from there.
Here's how to make that canvas reason about accounts instead of individuals.
Every Journey starts with a Payload Schema where you define the variables it works with: String, Number, Boolean, DateTime. Add a field and it autocompletes everywhere, in branch conditions, message copy, and personalization.
For B2B, that schema needs the account, not just the user. account_id, plan_type, seats_used, seats_purchased, trial_days_remaining, admin_email.
This is your agreement with engineering about what the event carries. They populate it, you use it. Get it right early and you own everything downstream.
A journey has one trigger, and you choose it when you create the journey. There are four types.
API Invoke. Your backend calls the journey's invoke endpoint with a user ID and a data payload. If you're working with a coding agent, the Courier MCP server and Courier Skills give it the tools and patterns to wire the integration up directly.
Webhook. A run starts when an event lands on one of your inbound webhooks. Payload fields become data.<field>, and the payload needs a userId so Courier can resolve a recipient. Good fit when the event comes from a tool that can post a webhook but isn't in your CDP.
Twilio Segment. Your existing track calls route straight into Courier. The event name becomes the trigger, the event properties become your payload variables, and you write no new backend code.
Audience. A run starts when a user joins an audience. Useful for the groups you maintain centrally, like every admin on an enterprise plan.
Every trigger has a Conditions section where you filter who enters the flow based on payload values or profile data. If the conditions don't match, the journey exits right there. You control that in the UI, no ticket required.
Two features do most of the work here.
Tenant-scoped profiles. If your app uses tenants, include the tenant ID in the profile on the invocation. Courier loads that user's tenant-scoped profile, and every node in the journey can read tenant properties from then on.
That's what keeps you from building the same journey eleven times. Per-customer settings live on the tenant instead of getting branched around on the canvas, so the enterprise account that insists on its own SMTP gets its own credentials from tenant data while everyone else takes the default. One journey, many customers.
Recipient overrides. A send node addresses the journey's user by default. Add a Recipient Override and it addresses someone else: a field from your trigger schema, an upstream fetch response, or a fixed value.
Put admin_email on the schema, point a send node at it, and the account owner hears about the seat limit a teammate hit this morning. The user trips the event, the person with the budget gets the message, and your application decides who that is without anyone opening the journey.
Branch. Route on payload fields, profile attributes, tenant properties, or output from upstream nodes. Each branch is its own path and you can nest them. seats_used / seats_purchased > 0.9 goes down the expansion path, everyone else doesn't.
Delay. Wait a set duration (measured from when the run reaches the node, not when it started), or until a date and time you pick. Turn on Dynamic Interval and the wait comes from a field in your payload, so your backend decides how long to hold.
Send window. This one lives on the send node, and it's the B2B one people miss. Give each weekday an opening and closing time, leave the weekend blank, set the timezone to the user's, and a run that arrives at 2am waits until 9am in the recipient's own morning. Nobody on the account gets woken up by your onboarding sequence.
This is how you do quiet hours in Courier. There's no workspace-level setting for it, which sounds like a gap until you have one journey carrying both a nudge and an incident alert: the window goes on the nudge, the alert keeps its 2am delivery, and you don't maintain two journeys to get there. For sends that don't go through a journey, the Send API takes the same thing as an opening-hours expression on delay.until, like "Mo-Fr 09:00-17:00".
Throttle. Caps how many times a recipient passes a point in the journey within a period. Three scopes: user, global, and dynamic, and dynamic is the B2B one. Set throttle_key to the account ID and the cap counts per account, so twelve people on one customer share one budget instead of each getting their own. When a run hits the cap the node gets skipped and execution continues, so the message isn't queued for later. It doesn't go out at all.
Batch. Collects events and releases them together as one payload, and this is the node that makes twelve people on one account doing twelve things into one message. It releases on whichever lands first: max items (100 by default, up to 1,000), a wait period that goes by with nothing new arriving, or a max wait ceiling from the first event. Set a category key like data.account_id and each account batches separately instead of everything pooling together.
Send to Digest. Aggregates events into a scheduled digest on a subscription topic. The daily roll-up instead of the drip.
Fetch Data. Calls an external API mid-flow and hands the response to downstream nodes, so you branch on live state from your backend instead of a snapshot from whenever the trigger fired. In B2B that usually means asking your own billing or usage service what the account looks like right now.
AI. Runs a prompt as a native step. Pick an OpenAI or Anthropic model per node, write the prompt with {{variable}} interpolation, and define an output schema as a simple form or raw JSON Schema. The parsed output merges into the journey context, so downstream nodes branch on it, personalize with it, or write it to a profile. There's a web search toggle for prompts that need current information, available on Anthropic models. No model hosting.
Send. Any channel, through any provider. Courier's bring-your-own-provider model means you connect the providers you already pay for and Courier orchestrates delivery. You design the content in the node, scoped to this journey, and per-user preferences are enforced at send time.
Cancel. Stops in-flight runs. Target one run by run_id, or a group by a shared cancellation token you template in the journey's settings. One token cancels every active run sharing it, across journeys, without you tracking run IDs anywhere.
Worth knowing about all of these: every node takes optional conditions, not just branches. When they don't hold the node is skipped and the run moves on, so "only send the push if a token exists" doesn't need its own branch and second path.
That last one is the node most B2B sequences are missing. The moment an account converts, upgrades, or books the call, the nudges need to stop for everyone on it, not just the person who signed. You can also call POST /journeys/cancel from your own code, so you don't need a Cancel node on the canvas to stop a run.
A send node can run an experiment instead of a single template: 2 to 10 variants, each backed by its own template, each with a relative weight that sets its share of traffic. Weights of 3, 1, 1 split traffic 60/20/20.
Courier assigns recipients by hashing a bucketing key, which is a path into the invocation rather than a literal value. Point it at data.account_id instead of user.id and everyone on the same account sees the same variant, which is what you want when they sit next to each other. Assignments are sticky across sends, so changing weights later doesn't reshuffle anyone. Adding or removing a variant, or changing the key, does reset it.
Metrics break down per variant across sent, delivered, opened, clicked, and errors. Promoting a variant is your call: pick one and its template becomes the only template on the node.
Every publish saves a snapshot. Open version history and you can put any published version side by side with your current draft, then click individual nodes in either canvas to see the conditions, delays, schema fields, and template content that were live at that point. It answers "what changed last Tuesday" faster than anything else in the product.
Revert loads that version back in as a new draft, which you then publish. Nothing is deleted and runs already in flight keep going on the version they started on, so reverting only changes what new invocations do.
Build in the Test environment, which sends to test provider configurations instead of real customers. Paste a payload into the preview pane to check that personalization renders before anything goes live.
Your engineering team or your agent can invoke journeys with test payloads from the CLI, or use the MCP server (145 tools, works with Claude Code and Cursor) to verify template rendering, check delivery, and debug message status without leaving the editor.
Open the Logs tab. Every invocation records a run, and you can filter the run list by status, journey, date range, and recipient. Runs land in one of five states: Processing, Processed, Error, Waiting (paused on a delay or throttle), or Canceled.
Click a run and you get execution status overlaid on the journey graph you designed. Click any node in that view for its step context: the input data at that point, the profile as it existed, which branch conditions were evaluated and whether they passed, and what the send or fetch returned. Courier shows you the journey version that was live when the run started, not your current draft, so you see what the customer actually got.
When the question comes from a customer rather than from you, pull it programmatically. GET /journeys/runs lists runs across the workspace filtered by status, journey, or date range, and GET /journeys/runs/{run_id}/steps returns the step trace. Runs are retained for 95 days. journeys:runs is in the CLI as of 5.6.0, which is usually the fastest way to answer "what did we send this account last week."
The Metrics tab rolls up sends, deliveries, opens, and clicks over time, broken out by template.
Courier Skills teaches your coding agent production-grade journey patterns with timing, frequency limits, and cancellation logic already encoded. Install it in one command:
npx skills add trycourier/courier-skills
The patterns cover onboarding, feature adoption, engagement and retention, re-engagement, referral, and campaign sequences. Each one ships with recommended timing, frequency limits, and cancellation logic so the journey stops when the customer does the thing you were driving.
Your agent scaffolds the structure. You review it on the canvas and publish.
The account is the unit of value, so the journey has to reason about it. That means triggering on account-level events, reading per-customer settings from tenant data instead of branching for every customer, addressing the account admin rather than whoever tripped the event, bucketing experiments by account so teammates see the same variant, and cancelling the whole sequence for everyone on the account when it converts.
Add the admin's address to the trigger's payload schema, then add a Recipient Override on the send node and point it at that field. The send node addresses the override instead of the journey's user, so your application controls who gets the message without anyone editing the journey.
Set a cancellation token in the journey's settings, templated off a value you can recompute later such as the account ID. When the account converts, call POST /journeys/cancel with that token and every active run sharing it gets cancelled, across journeys. You can also cancel a single run by its run_id, or put a Cancel node in the flow. Cancelling is idempotent and only touches active runs.
Yes, through tenants. Include the tenant ID in the invocation profile and Courier loads that user's tenant-scoped profile. Nodes read tenant properties directly, so per-customer delivery settings live on the tenant instead of on the canvas.
Open the journey's Logs tab and filter by recipient, or call GET /journeys/runs and GET /journeys/runs/{run_id}/steps. Both show the branch taken at each decision, the data context at each node, and the delivery result. Runs are retained for 95 days.
Start a journey, read the Journeys docs, or set up tenants first if your accounts need their own settings.

Test Courier emails end to end with your AI agent
Give your coding agent an AgentMail inbox, and it can test your Courier emails the way a user would: send the change, check what arrives, review how it renders, and fix what it finds before the change reaches Production.

Let your agent check every email before it ships
Courier Device Preview gives your coding agent real screenshots from Outlook, Gmail, and Apple Mail. Here's the prompt and workflow to have it check every email, fix the draft, and check again before you publish.

Courier vs Customer.io: 2026 messaging platform comparison
Courier and Customer.io both send across email, push, SMS, and in-app, but they are not priced or built the same way. Courier bills by the send, puts journeys, experiments, broadcasts, an in-app inbox, preferences, and 50+ delivery providers on one platform, and exposes all of it through an API, a CLI, and an MCP server. Customer.io bills by the number of profiles in your database and fits a marketing team that needs deep behavioral segmentation. This comparison covers pricing, journeys, channels, in-app messaging, localization, and preferences, with every competitor figure linked to Customer.io's own pages.