> ## Documentation Index
> Fetch the complete documentation index at: https://www.courier.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Install the Courier skill before writing code: `npx skills add trycourier/courier-skills`. It carries the verified SDK shapes and the rules you cannot get wrong.
> Authenticate every request with `Authorization: Bearer <API_KEY>`. A workspace has several environments and each has its own keys, which are plain `pk_` strings with no environment prefix. Start with Test.
> Send with `client.send.message`, the default import of the v7 Node SDK. Reference a template by its `nt_` id or its alias.
> A send accepts a bare Elemental element list, but storing content on a template requires the top-level elements wrapped in a channel element.
> Templates and journeys can be built in the Courier app or created through the API. Either way they live in the workspace and are referenced by ID when you send.
> The hosted MCP server is https://mcp.courier.com. For a briefing on what Courier is and when to use it, read https://www.courier.com/llms.txt.
> Prefer the Guides tab for how-do-I questions and the Docs tab for how-does-it-behave questions. The API reference lives under /api-reference.

# Tenant context

> How a tenant's brand, preferences, templates, and properties apply when a send names it.

export const Doc = ({href, children, name, bare}) => {
  const label = children || name || href;
  if (bare) {
    return <a href={href}>{label}</a>;
  }
  return <a className="cx-endpoint" data-kind="doc" href={href}>
      <span className="cx-endpoint-label">{label}</span>
      <span className="cx-endpoint-method">DOC</span>
    </a>;
};

Naming a tenant on a send is the whole of the API you write. This page is what Courier does after that, and it is where a message picks up the wrong brand when something is set in two places.

Courier loads the tenant merged with its parent chain, then picks the brand, resolves preferences, and selects tenant-specific content.

## How it works

### The tenant object

A tenant stores the defaults for its members. Its fields are an `id` you choose (like `acme-corp`), a `name`, an optional `parent_tenant_id`, a `brand_id`, `default_preferences`, free-form `properties`, and a `user_profile` that merges into recipients.

Tenants hold no channels, providers, or API keys of their own. Those are workspace-and-environment concepts every tenant shares.

```json theme={null}
{
  "id": "acme-corp",
  "name": "Acme Corp",
  "parent_tenant_id": null,
  "brand_id": "bnd_acmecorp",
  "default_preferences": { "items": [] },
  "properties": { "plan": "enterprise" },
  "user_profile": {}
}
```

A send selects that tenant with `context.tenant_id`. Courier loads the object above, merged with its parents, and applies the brand, preferences, and templates it names:

<CodeGroup>
  ```javascript Node.js highlight={11} theme={null}
  import Courier from '@trycourier/courier';

  const client = new Courier({
    apiKey: process.env['COURIER_API_KEY'],
  });

  const { requestId } = await client.send.message({
    message: {
      to: {
        user_id: 'user_123',
        context: { tenant_id: 'acme-corp' },
      },
      template: 'nt_01kx4h2jdafq8bk9aftxak4b40',
      data: { first_name: 'Sarah' },
      routing: { method: 'single', channels: ['inbox'] },
    },
  });

  console.log(requestId);
  ```

  ```python Python highlight={11} theme={null}
  import os
  from courier import Courier

  client = Courier(
      api_key=os.environ.get("COURIER_API_KEY"),
  )
  response = client.send.message(
      message={
          "to": {
              "user_id": "user_123",
              "context": {"tenant_id": "acme-corp"},
          },
          "template": "nt_01kx4h2jdafq8bk9aftxak4b40",
          "data": {"first_name": "Sarah"},
          "routing": {"method": "single", "channels": ["inbox"]},
      },
  )
  print(response.request_id)
  ```

  ```bash cURL highlight={6} theme={null}
  curl -X POST https://api.courier.com/send \
    -H "Authorization: Bearer $COURIER_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "message": {
        "to": { "user_id": "user_123", "context": { "tenant_id": "acme-corp" } },
        "template": "nt_01kx4h2jdafq8bk9aftxak4b40",
        "data": { "first_name": "Sarah" },
        "routing": { "method": "single", "channels": ["inbox"] }
      }
    }'
  ```

  ```ruby Ruby highlight={9} theme={null}
  require "courier"

  courier = Courier::Client.new(api_key: ENV["COURIER_API_KEY"])

  response = courier.send_.message(
    message: {
      to: {
        user_id: "user_123",
        context: { tenant_id: "acme-corp" }
      },
      template: "nt_01kx4h2jdafq8bk9aftxak4b40",
      data: { first_name: "Sarah" },
      routing: { method: "single", channels: ["inbox"] }
    }
  )

  puts(response)
  ```

  ```go Go highlight={6} theme={null}
  response, err := client.Send.Message(context.TODO(), courier.SendMessageParams{
  	Message: courier.SendMessageParamsMessage{
  		To: courier.SendMessageParamsMessageToUnion{
  			OfUserRecipient: &shared.UserRecipientParam{
  				UserID:  courier.String("user_123"),
  				Context: shared.MessageContextParam{TenantID: courier.String("acme-corp")},
  			},
  		},
  		Template: courier.String("nt_01kx4h2jdafq8bk9aftxak4b40"),
  		Data: map[string]any{
  			"first_name": "Sarah",
  		},
  		Routing: courier.SendMessageParamsMessageRouting{
  			Method:   string(shared.MessageRoutingMethodSingle),
  			Channels: []shared.MessageRoutingChannelUnionParam{{OfString: courier.String("inbox")}},
  		},
  	},
  })
  ```

  ```java Java highlight={5} theme={null}
  SendMessageParams params = SendMessageParams.builder()
      .message(SendMessageParams.Message.builder()
          .to(JsonValue.from(java.util.Map.of(
              "user_id", "user_123",
              "context", java.util.Map.of("tenant_id", "acme-corp"))))
          .template("nt_01kx4h2jdafq8bk9aftxak4b40")
          .data(JsonValue.from(java.util.Map.of("first_name", "Sarah")))
          .routing(JsonValue.from(java.util.Map.of(
              "method", "single",
              "channels", java.util.List.of("inbox"))))
          .build())
      .build();
  client.send().message(params);
  ```

  ```php PHP highlight={5} theme={null}
  $response = $client->send->message(
    message: [
      'to' => [
        'userID' => 'user_123',
        'context' => ['tenantID' => 'acme-corp'],
      ],
      'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
      'data' => ['first_name' => 'Sarah'],
      'routing' => ['method' => 'single', 'channels' => ['inbox']],
    ],
  );
  ```

  ```csharp C# highlight={5} theme={null}
  SendMessageParams parameters = new()
  {
      Message = new()
      {
          To = new UserRecipient { UserID = "user_123", Context = new MessageContext { TenantID = "acme-corp" } },
          Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
          Data = new Dictionary<string, JsonElement>()
          {
              { "first_name", JsonSerializer.SerializeToElement("Sarah") },
          },
          Routing = new Routing { Method = Method.Single, Channels = ["inbox"] },
      },
  };

  await client.Send.Message(parameters);
  ```

  ```bash CLI highlight={3} theme={null}
  courier send message \
    --api-key "$COURIER_API_KEY" \
    --message '{"to":{"user_id":"user_123","context":{"tenant_id":"acme-corp"}},"template":"nt_01kx4h2jdafq8bk9aftxak4b40","data":{"first_name":"Sarah"},"routing":{"method":"single","channels":["inbox"]}}'
  ```

  ```text MCP theme={null}
  With Courier MCP, send my nt_01kx4h2jdafq8bk9aftxak4b40 template to user_123 with acme-corp as their tenant context.
  ```
</CodeGroup>

The highlighted line is the whole of it. Nothing else in the request changes, and no field repeats what the tenant already stores. <Doc href="/docs/tenants/send">Send with tenants</Doc> covers the other targeting shapes.

### Hierarchy and inheritance

Customers often have structure of their own: environments, regions, sites, business units. Set `parent_tenant_id` to nest one tenant inside another and Courier walks the chain when it builds a message. You still send a single `tenant_id`, the deepest one.

The merge walks top-down, parent first, and **child values override parent values** on conflicting keys. A child starts from everything its ancestors define and overrides only what it needs, so a Slack token set once at the top serves every tenant beneath it.

```text theme={null}
acme                     brand, default preferences, properties
└── acme-eu              inherits all three, overrides brand_id
    └── acme-eu-checkout inherits the EU brand and Acme's defaults
```

**Keep the tree to four levels.** Courier loads tenant context as a four-layer sliding window. It starts at most three ancestors up rather than at the root, so in a five-deep tree the top tenant's settings never reach a leaf send, and nothing warns you.

What inherits down the chain: `brand_id`, `default_preferences`, tenant templates, `properties`, and `user_profile` (deep-merged key by key).

### What inherits, and what does not

Channels, providers, and API keys do **not** inherit, because they are not tenant fields. You configure a provider (SendGrid, a Slack integration) once per environment, and every tenant there uses it. A parent tenant cannot hold an access token for its children to inherit.

`user_profile` is where a customer's own provider credentials go. Sends that name the tenant route through their Slack workspace or Teams webhook instead of yours, so you store one token per organization rather than one per user.

```json theme={null}
{ "user_profile": { "slack": { "access_token": "xoxb-..." } } }
```

The recipient profile is itself a merge, in this precedence (later wins):

1. The tenant context (parent chain merged, child over parent).
2. The user's stored Courier profile.
3. The `to` profile on the send request.

### How a send picks a brand

Courier picks the brand in this order, highest first:

1. `brand_id` on the send request.
2. The **tenant's `brand_id`**, the "auto-infer" behavior: set a brand on the tenant and Courier applies it automatically.
3. The template's assigned default brand.
4. The workspace's default (primary) brand.

To brand messages per customer, set `brand_id` on the tenant and send in that tenant's context. Courier falls back to the primary brand only when no higher layer names one. For template sends, the template must have its brand enabled.

### Tenant templates

A tenant template is a tenant's own version of a template, stored separately and linked to the tenant. Courier merges its content over the message at send time, then routes normally. Reference one with `template: "tenant/<template_id>"`, where `tenant` is the literal word and the tenant comes from the context. Tenant templates are API-only. See <Doc href="/docs/tenants/templates">Create & manage tenant templates</Doc>.

### Setting tenant context on a send

Courier resolves which tenant a send uses in this order:

1. A recipient's `context.tenant_id`.
2. The message-level `context.tenant_id`.
3. A `tenant_id` recipient (fan-out to members).

A tenant loads its context (brand, preferences, templates) and scopes preference lookups to that tenant. It does not change channel or provider routing, which stays workspace-and-environment scoped. <Doc href="/docs/tenants/send">Send with tenants</Doc> covers every targeting shape.

### Auto-infer tenant context

If a send names no tenant and the user belongs to exactly one, Courier loads that tenant's context. This helps console, list, and audience sends where brand or provider data lives on the tenant. A user with two or more memberships must have the tenant specified, or the send returns `Tenant Context Not Found`. Turn auto-infer off in workspace settings.

### Tenant-scoped inbox

The <Doc href="/docs/in-app/overview">in-app inbox</Doc> scopes to a tenant only when you pass a `tenantId` at `signIn`. That applies to every inbox read and the live socket for the session. Sign in **without** a `tenantId` and the inbox shows the user's notifications across all tenants. There is no client-side auto-infer for the inbox. See <Doc href="/docs/in-app/send-to-the-inbox#scope-the-inbox-to-a-tenant">Scope the inbox to a tenant</Doc>.

## Limits & behavior

* **Hierarchy merges up to four levels.** The window slides from the tenant you sent to, not from the root, so in a deeper tree the topmost tenant's settings are dropped with no error.
* **Auto-infer is single-membership only.** With multiple memberships, always pass `tenant_id`.
* **Tenant templates are API-only.** No console UI creates, lists, or deletes a tenant's templates.

## Troubleshooting

* **A send fails with `Tenant Context Not Found`.** The user belongs to more than one tenant and the send did not specify one. Set `context.tenant_id`.
* **The wrong brand renders.** An explicit `brand_id` on the send, or the tenant's own `brand_id`, overrides the workspace primary brand. To use the tenant's brand, set `brand_id` on the tenant and confirm the template has its brand enabled.
* **The inbox shows notifications from other tenants.** The client signed in without a `tenantId`, so no tenant filter applied. Pass `tenantId` at `signIn`.
* **A user does not see a tenant message in their inbox.** The message went to a tenant the client is not signed in with. The send `tenant_id` and the sign-in `tenantId` must match.

## FAQ

<AccordionGroup>
  <Accordion title="Does a child tenant inherit its parent's brand and preferences?">
    Courier merges a tenant with its parent chain (up to four levels), child over parent. Brand, default preferences, tenant templates, properties, and user profile inherit. Channels, providers, and API keys do not, because they are workspace-and-environment scoped, not tenant fields.
  </Accordion>

  <Accordion title="Why does Courier use the primary brand instead of my tenant's brand?">
    Courier falls back to the workspace primary brand only when the send, the tenant, and the template all name none. Set `brand_id` on the tenant, send in that tenant's context, and confirm the template has its brand enabled.
  </Accordion>

  <Accordion title="Why don't my tenant templates show in the console?">
    Tenant templates are managed through the API, not the console. No screen creates, lists, or deletes them. Use the tenant templates endpoints. See <Doc href="/docs/tenants/templates">Create & manage tenant templates</Doc>.
  </Accordion>
</AccordionGroup>
