Blog
GUIDE

Build Multi-tenant Customer Messaging the Right Way | Branding, User Preferences, Routing

Thomas Schiavone

January 22, 2026

How to build multitenant customer messaging

Table of contents

Who this guide is for

The mental model (and how to map your platform)

Tenant hierarchy: modeling real org structure (and why you’ll care)

What data belongs on a tenant (and why you’d actually store it)

The one rule that prevents tenant leaks: always send with tenant context

Targeting groups inside a tenant (roles, teams, segments)

Preferences: stop treating them as global

Tenant-scoped inbox (optional, but huge if you have in-app)

Common mistakes (and how to avoid them)

Key takeaways

Next steps

Most B2B SaaS teams don’t plan to build multi-tenant notification infrastructure. They get there when customers start asking for “our branding,” “our routing rules,” “our Slack workspace,” and “our default preferences.”

The challenge isn’t sending more notifications—it’s sending notifications with the right customer context, every time, without threading a bunch of per-customer logic through your codebase.

Courier Tenants lets you set customer-level defaults once (branding, preferences, metadata, credentials) and have Courier apply them automatically whenever you send with tenant context.


Who this guide is for

This is for developers building multi-tenant B2B platforms—where your customers are businesses, and you send notifications on their behalf to their users (employees, admins, managers).

If you’ve ever heard:

  • “Can these notifications look like our brand?”
  • “Can we notify our admins but not everyone?”
  • “This person belongs to multiple orgs—why did they get the wrong notification?” …you’re exactly the audience.

The mental model (and how to map your platform)

The easiest way to think about Courier Tenants is: you’re not just sending notifications to users. You’re sending notifications to users in a specific customer context.

In a multi-tenant platform, the same user can belong to multiple orgs. Their identity isn’t just “User 123.” It’s:

  • “User 123 as a member of Acme Corp
  • “User 123 as a member of Beta Industries

Those contexts have different expectations:

  • Branding: this should look like Acme Corp
  • Preferences: Acme Corp and Beta Industries have different defaults, and users can override per tenant
  • Groups: “admins” means different people per tenant
  • Inbox visibility: in-app streams should not blend org contexts

The simplest mapping (that holds up)

  • Tenant = your customer’s organization/workspace/account (e.g., tenant_acme)
  • Users = the people inside that tenant (employees, admins, managers)
  • Tenant context = the scope you include when you send so branding + preferences + inbox behavior stay correct

The important part: you only get the value of Tenants when you send with tenant context consistently. See: Sending with tenants.


Tenant hierarchy: modeling real org structure (and why you’ll care)

Real customers aren’t flat. They have departments, teams, projects, and environments—and notifications often need to respect that structure.

Courier Tenants support parent → child relationships (up to four layers) so you can model org structure directly. (Tenants overview, Tenant deep dive)

A basic visual (example: HR platform)

Copied!

tenant_acme (Company)
├─ tenant_acme_hr (Department)
│ ├─ tenant_acme_hr_recruiting (Team)
│ └─ tenant_acme_hr_peopleops (Team)
└─ tenant_acme_eng (Department)
├─ tenant_acme_eng_platform (Team)
└─ tenant_acme_eng_product (Team)

What hierarchy buys you (in practice)

Hierarchy is less about “pretty structure” and more about inheritance:

  • Put shared defaults at the company level (branding, baseline preferences, shared metadata)
  • Override where needed at lower levels (department/team-specific defaults and context)
  • Target at the right scope: all of Acme Corp, only Acme Corp HR, or just Acme Corp HR Recruiting

It also keeps your model sane when customers ask for “department-level” behavior without you duplicating configuration everywhere.


What data belongs on a tenant (and why you’d actually store it)

A tenant shouldn’t just be “an ID.” It’s the customer context you want Courier to apply automatically whenever you send notifications on that customer’s behalf.

brand_id: make it look like them by default

Attach a tenant’s brand once so you don’t have to pass branding choices on every send.

Value:

  • Prevent “wrong logo” / inconsistent styling bugs
  • Make white-label experiences repeatable
  • Keep send calls simple and consistent

default_preferences: customer defaults, before users customize

Set tenant-level defaults so new users start with sensible behavior for that customer.

Value:

  • Different customers can have different baselines (no global preference trap)
  • Reduces preference chaos during onboarding
  • Helps prevent notification fatigue

properties: template-friendly tenant metadata

Arbitrary tenant metadata that templates can reference at render time.

Value:

  • One template can render differently per tenant without custom branching in your app
  • You don’t need to pass the same customer metadata on every send

Common examples:

  • companyName, companySlug
  • timezone, locale
  • supportEmail, helpCenterUrl
  • Plan tier or feature flags that affect messaging

user_profile: tenant-scoped recipient defaults

Think “defaults you want merged into the recipient profile when sending within this tenant context.”

Value:

  • Consistent template variables without repeated send payloads
  • Helps when profile defaults depend on customer context

Provider credentials: true white-label delivery

In real white-label setups, different customers often want different delivery plumbing. Tenants can store provider credentials (like a Slack workspace token, a Microsoft Teams webhook URL, or custom SMTP credentials) so Courier can load them automatically when you send with that tenant context.

That means you don’t have to thread “which Slack workspace is this?” through every send call—you attach it to the tenant once.

parent_tenant_id: org structure + inheritance

Use this to build hierarchies and inherit shared defaults.

Value:

  • Less duplication
  • Better scoped targeting
  • Cleaner mental model for departments/teams/projects

The one rule that prevents tenant leaks: always send with tenant context

When a user can belong to multiple customers, a notification isn’t just “to user X.” It’s “to user X as a member of tenant Y.” Tenant context is what keeps branding, preferences, and inbox visibility scoped correctly.

See: Sending with tenants.

Copied!

import Courier from "@trycourier/courier";
const courier = new Courier({
apiKey: process.env.COURIER_API_KEY,
});
await courier.send({
message: {
to: {
user_id: "customer_123",
context: {
tenant_id: "tenant_acme",
},
},
content: {
title: "Invoice available",
body: "Your invoice from Acme Corp is ready.",
},
},
});

A practical way to make this reliable in your codebase: treat tenantId as a required parameter anywhere you send notifications. If you don’t have a tenant context, you shouldn’t be sending a tenant-scoped notification.


Targeting groups inside a tenant (roles, teams, segments)

Tenants define the customer boundary. But “groups” inside that boundary (roles, teams, segments) can live in a few places—what matters is where your source of truth is and whether you want Courier to handle fan-out.

Here are three common approaches:

  • Option 1: Your own DB

    • Keep membership in your app (roles/permissions/team membership).
    • Query “who should get this?” within the tenant, then send to those user_ids with to.context.tenant_id.
  • Option 2: Courier Lists

    • Store membership in a Courier List (e.g., acme.admins, acme.beta-testers) and send to the list.
    • This is useful when you want Courier to manage fan-out and you can keep list membership in sync.
  • Option 3: Sub-tenant

    • Model the group as a child tenant (department/project/workspace) and manage membership via tenant membership.
    • This fits best when the “group” is really an org scope and you want tenant defaults (branding, preferences, metadata, provider credentials) to apply at that level.

Rule of thumb: if it’s authorization logic → keep it in your DB (and optionally sync to Lists). If it’s organizational structure → use sub-tenants. If it’s a segment you want Courier to fan out to → use Lists.

For more detail on higher-level targeting patterns (tenant members, nested hierarchies), see: Sending with tenants.

A few tenant targeting patterns you’ll use a lot

Most apps start with “query users in my DB, then send,” but Courier also supports tenant-level fan-out patterns that are perfect for org-wide announcements.

Send to all tenant members (no user list needed)

If you want to notify everyone in a tenant, you can send directly to the tenant:

Copied!

{
"to": {
"tenant_id": "tenantA"
}
}

Courier will fan out the send to all users who have a membership in that tenant—while applying tenant-scoped defaults (branding, preferences, metadata, and provider credentials).

Fan out across a hierarchy with include_children

If tenant_acme has child tenants (departments/teams), you can target everyone under the company with:

Copied!

{
"to": {
"tenant_id": "tenant_acme",
"include_children": true
}
}

This is the “company-wide announcement” button.

Go up the chain with include_parent

Sometimes you’re sending from a sub-tenant but want to include parent members too (like a department notifying the whole company):

Copied!

{
"to": {
"tenant_id": "tenant_acme_hr",
"include_parent": true
}
}

Preferences: stop treating them as global

In multi-tenant platforms, preferences are contextual. Users don’t have one preference profile—they have preferences per tenant context.

Courier supports tenant-scoped defaults users can override:

A pragmatic rollout:

  • Set tenant defaults that match how each customer expects notifications to behave
  • Let users override per tenant so they don’t mute everything globally

If you’re using hierarchy, this becomes even more useful: a company-level default can apply across the org, while departments/teams override where needed.


Tenant-scoped inbox (optional, but huge if you have in-app)

If you provide an in-app notification feed, tenant context keeps inbox experiences from collapsing into one blended stream.

Start here: Inbox with tenants.

This pairs naturally with an org switcher:

  • Switch org → switch tenant context → inbox shows the right messages

One easy gotcha: messages sent with tenant context only show up when your Inbox SDK is initialized with the same tenantId. And if you’ve created tenant memberships (and especially if auto-infer is enabled), you still need to set tenantId when you initialize the SDK—otherwise messages can “disappear” because the SDK is looking at a different tenant inbox.


Common mistakes (and how to avoid them)

Mistake 1: “We created tenants, but nothing changed”

You’re probably not sending with tenant context consistently. Start here: Sending with tenants.

Mistake 2: “Preferences are confusing, users opt out of everything”

You probably modeled preferences globally. Make them tenant-aware: User tenant preferences.

Mistake 3: “Branding is inconsistent”

Branding is being handled ad-hoc (per send, per template, per channel). Move the defaults into tenant configuration: Tenants overview.

Mistake 4: “Our inbox mixes org contexts”

Use tenant-scoped inbox rendering and tenant-scoped sends: Inbox with tenants.


Key takeaways

  • Tenant = customer context: Map tenants to your customers' organizations and attach branding, preferences, metadata, and provider credentials once
  • Always send with tenant context: Include tenant_id on every send to prevent branding mismatches, preference leaks, and inbox blending
  • Use hierarchy for inheritance: Parent-child relationships (up to 4 levels) reduce duplication and enable scoped targeting
  • Preferences are per-tenant: Users can have different notification settings for each organization they belong to

Next steps

A solid order of operations for implementation:

  1. Understand the model and key features: Tenants overview
  2. Go deeper on hierarchy and merging: Tenant deep dive
  3. Implement tenant-aware sends: Sending with tenants
  4. Add per-tenant preference support: User tenant preferences
  5. If you have an in-app feed, scope it: Inbox with tenants

And if you want an example of a platform scaling notifications without turning it into a maintenance sink, Side’s story is a good read: How Side unified notifications.

Similar resources

alert fatigue | notifications
GuideProduct Management

What Is Alert Fatigue?

Alert fatigue occurs when users become desensitized to notifications due to high volume, leading to ignored alerts, missed critical information, and decreased engagement. This problem affects product notifications, DevOps monitoring, healthcare systems, and security operations. This guide covers the psychology behind alert fatigue (habituation and the "cry wolf" effect), how to measure it (open rates, dismiss rates, time-to-action), and five practical strategies to reduce it: batching, prioritization, user preferences, smart channel routing, and timing optimization.

By Kyle Seyler

January 23, 2026

what is observability
GuideIntegrationsEngineering

Notification Observability: How to Monitor Delivery, Engagement, and Provider Health

Notification observability is the practice of monitoring notification delivery, engagement, and provider health using the same tools and discipline you apply to the rest of your application infrastructure. It means tracking whether messages are delivered, opened, and acted on across email, SMS, push, and in-app channels, then surfacing that data in dashboards alongside your other application metrics. Key metrics include delivery rate by channel, bounce and failure rates, provider latency, open rate trends, and click-through rates by template. Teams can build notification observability through DIY webhook handlers that pipe provider events to Datadog or Prometheus, log aggregation from application send logs, or notification platforms with built-in observability integrations. This matters most for multi-channel systems, business-critical notifications like password resets and payment confirmations, and teams using multiple providers with fallback routing.

By Kyle Seyler

January 15, 2026

Multichannel Notification Template Management
GuideUser ExperienceIntegrationsNotifications Landscape

Multichannel Notification Template Management: Version Control, Migration, and Cross-Channel Previews

AI agents are reshaping how products communicate with users. By 2026, 40% of enterprise applications will embed agents that need to send notifications across email, SMS, push, Slack, Teams, and in-app channels autonomously. Managing templates across all these channels with Git-based workflows doesn't scale. This guide covers how teams handle version control and rollback for multichannel templates, which platforms enable designer collaboration without deploys, whether Figma design systems can connect to notification builders, how to migrate templates using APIs and MCP-assisted workflows, how to preview messages across channels side-by-side, open-source options that integrate with SendGrid, Twilio, Firebase, and Slack, and how to localize content from one dashboard. Platforms covered include Courier, Novu, Knock, SuprSend, Dyspatch, Email Love, and React Email, with honest assessments of limitations for each.

By Kyle Seyler

January 14, 2026

Multichannel Notifications Platform for SaaS

Products

Platform

Integrations

Customers

Blog

API Status

Subprocessors


© 2026 Courier. All rights reserved.