
If you sell to businesses, the unit you send notifications for is rarely a single user. It's the customer account: the company, workspace, or team that a group of your users belongs to. Each account expects its own branding, its own defaults for what gets sent, and sometimes its own wording. Build that into every send by hand and you end up with a settings lookup in front of every notification you ship.
A Courier tenant is how you model that account. It stores a brand, default preferences, properties, and optional template overrides, and you name it with one field on the send. Courier applies the rest.
Whatever you build on, four things have to be true per customer account:
You can assemble these in your own code: a table of per-account settings, a lookup before each send, and a rendering step that swaps the brand. It gets heavy once you add more channels and a customer who wants to edit their own emails. Tenants move that lookup into the notification layer.
A tenant is a record you create with an ID you choose, so it can reuse the account ID from your own database.
| What you store | Where it goes |
|---|---|
| Logo, colors, and email header and footer | brand_id, pointing at a brand |
| What's on or off for everyone at that company | default_preferences |
| Plan, region, support address, account manager | properties, readable from template content |
| Their own version of one of your templates | A tenant template |
| Their connected Slack workspace or Microsoft Teams bot details | user_profile |
| Their place under a parent organization | parent_tenant_id |
Anything the tenant doesn't define falls through to your workspace default, so you only configure the differences.
Tenants scope branding and settings, not infrastructure or access.
user_profile, so sends that name the tenant go through their Slack workspace.If a customer needs a hard boundary of its own, with separate providers, API keys, and billing, that's a separate workspace. The workspaces page lays out when to pick a workspace, an environment, or a tenant.
PUT /tenants/{tenant_id} creates the tenant or replaces it, so the same call works at signup and on every change after. Point it at the brand you made for that customer:
import Courier from "@trycourier/courier";
const courier = new Courier({ apiKey: process.env.COURIER_API_KEY });
await courier.tenants.update("acme-corp", {
name: "Acme Corp",
brand_id: "bnd_acmecorp",
properties: { plan: "enterprise", support_email: "help@acme.example" },
});Then add the account's users to it. A user can belong to as many tenants as you like, and each membership can carry its own profile data:
await courier.users.tenants.addSingle("acme-corp", {
user_id: "user_123",
profile: { role: "admin" },
});To put one user in several tenants in one call, use the add a user to multiple tenants endpoint.
Naming the tenant is the whole change to your send. Put context.tenant_id on the recipient, and Courier applies that tenant's brand, defaults, and content:
const { requestId } = await courier.send.message({
message: {
to: {
user_id: "user_123",
context: { tenant_id: "acme-corp" },
},
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
data: { first_name: "Sarah" },
},
});Inside a Design Studio template, tenant values are variables like {{tenant.name}} and {{tenant.properties.plan}}. Put customer-level values in the tenant's properties, not in data. A support address that arrives in data on every send belongs in properties once.
Courier picks the brand in this order, highest first:
brand_id on the send request.brand_id.So setting brand_id on the tenant is enough, as long as the template has its brand enabled. You don't pass a brand on the send.
To reach every member, put a tenant_id in to instead of a user:
await courier.send.message({
message: {
to: { tenant_id: "acme-corp" },
template: "nt_01kx4h2jdafq8bk9aftxak4b40",
routing: { method: "single", channels: ["inbox"] },
},
});Courier looks up the tenant's members and sends to each one. You can also send to an array of users with a shared message.context.tenant_id and override it on individual recipients, which is handy when one person appears in two accounts. Courier records the tenant on each message at providers[].reference.tenantId, so you can group logs and delivery data by customer afterward, from the message record or a delivery webhook.
By default, a user in exactly one tenant gets that tenant inferred if the send doesn't name one (you can turn auto-infer off in workspace settings). A user in two or more must have context.tenant_id set, or the send fails with Tenant Context Not Found. Pick one place for it, on the recipient or on the message, and put it behind a shared helper in your code. Chasing down a notification that rendered with the wrong brand is much harder when two call sites disagree.
The in-app inbox follows the same rule. A tenant-scoped inbox message only appears to a client that signs in with the same tenantId.
A tenant's default_preferences set a topic's starting state for everyone in that organization. Each entry is a topic ID and a status of OPTED_IN, OPTED_OUT, or REQUIRED:
await courier.tenants.preferences.items.update("pt_01kx4h2jdafq8bk996nn92357r", {
tenant_id: "acme-corp",
status: "OPTED_OUT",
});Preferences resolve in layers, and each layer overrides the one above it:
tenant_id to the preferences API.Layer 5 is what lets one person leave alerts on at one customer and off at another. A tenant default isn't a lock, because a user who opts out stays opted out. For a topic nobody should be able to switch off, like a security alert, set its status to REQUIRED, the only status that ignores what the user chose. The full merge order is in the preferences overview.
A tenant default sets a topic's status and nothing else. Channel routing is read from each user's own preference record, so set routing on the user's preferences.
When a customer needs its own copy and not only its own colors, create a tenant template. It overrides one of your templates for that tenant, and everyone else keeps the default. Tenant templates are API-only. Create one, then publish it:
await courier.tenants.templates.replace("welcome", {
tenant_id: "acme-corp",
template: {
content: {
version: "2022-01-01",
elements: [
{
type: "channel",
channel: "email",
elements: [{ type: "text", content: "Welcome to Acme Corp!" }],
},
],
},
},
});
await courier.tenants.templates.publish("welcome", { tenant_id: "acme-corp" });Then reference it with template: "tenant/welcome" and the tenant in the context. The word tenant is literal, and your tenant ID goes in context.tenant_id. Leave the tenant/ prefix off and the send still succeeds, but it renders your workspace template, because a template ID that resolves isn't an error.
To let customers write that wording themselves, embed the designer in your app. It runs in the browser, so it authenticates with a JWT instead of your API key. You issue the token from your backend with a scope limited to one tenant:
const { token } = await courier.auth.issueToken({
scope: "user_id:user_123 tenant:acme-corp:read tenant:acme-corp:notifications:read tenant:acme-corp:notifications:write",
expires_in: "30 days",
});These scopes are the designer's own. They limit the token to one tenant's templates and aren't a permission layer for your other API calls, which keep using your workspace API key. The let customers edit notifications guide walks through the full setup.
Customers have structure of their own: regions, sites, business units. Set parent_tenant_id to nest one tenant inside another, and Courier walks the chain when it builds a notification. You still send to the deepest tenant.
await courier.tenants.update("acme-eu", {
name: "Acme EU",
parent_tenant_id: "acme-corp",
brand_id: "bnd_acme_eu",
});The merge goes top-down and child values override parent values. Brand, default preferences, tenant templates, properties, and user_profile inherit, so a Slack token set once at the top serves every tenant beneath it. Four things to know:
include_children: true to a tenant send to reach descendant tenants, or include_parent: true to walk up. Every recipient is evaluated against the tenant you sent to, so if children keep their own preferences, send to each child directly.Create a brand for one customer, create the tenant, add a user, and send to them with context.tenant_id. The notify across tenants guide walks through that flow and how to verify each step, and the Developer plan includes 10,000 messages a month free. Sign up for Courier to try it, or talk to our engineers if your hierarchy is more involved.
No. Tenants serve multiple customer organizations from one account, as in B2B SaaS or white-label products. A single-customer app needs one workspace and no tenants.
No. A tenant inherits every workspace template by default. Create a tenant template only for the notifications that customer needs to differ.
No. A tenant maps to whatever unit your product bills or brands: a team, a project, a store, or a region.

How to Use Claude Code on Mobile to Design, Test, and Ship Multichannel Notifications
A walkthrough of using Claude Code's mobile app with Courier's MCP server and CLI to design, test, and ship a multichannel product announcement from a phone. Covers the full workflow: drafting the content outline, creating notification templates using Courier's elemental format, building channel-specific variants for email, Slack, push, and SMS, customizing brand styling through the API, iterating on design with test sends, getting team approval, and publishing with custom routing rules. The whole thing took a couple hours, mostly cold start and design iterations.

Transactional vs. product vs. marketing notifications
Transactional notifications confirm something the user did or something that happened to their account. Product notifications help users get value from features they already have. Marketing notifications sell. The consent rules differ by channel: marketing email is opt-out under CAN-SPAM, marketing texts need prior express written consent under the FCC's TCPA rules, and every push notification is gated by the operating system's notification permission.

How to Add Toast Notifications with the New Courier Toasts SDK
Learn how to add real-time, customizable toast notifications to your app with the Courier Toasts SDK. This quick tutorial shows how to integrate toasts using Web Components or React and sync them with your notification center for a seamless, modern UX.