> ## 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.

# Notify across tenants

> Send from one workspace to many customer organizations, each with its own brand and defaults.

export const Tags = ({items}) => {
  const routes = {
    Email: "/integrations/email/overview",
    SMS: "/integrations/sms/overview",
    Push: "/integrations/push/overview",
    Inbox: "/in-app/overview",
    Chat: "/integrations/direct-message/overview",
    Templates: "/design/templates/overview",
    Variables: "/design/templates/variables",
    Elemental: "/design/elemental/overview",
    Brands: "/design/brands",
    Translations: "/design/elemental/locales",
    Routing: "/send/routing",
    Preferences: "/recipients/preferences/overview",
    Journeys: "/journeys/overview",
    Broadcasts: "/broadcasts/overview",
    Tenants: "/tenants/overview",
    Logs: "/monitor/overview",
    Webhooks: "/monitor/webhooks/outbound",
    Lists: "/recipients/lists-and-audiences/overview",
    Users: "/recipients/overview",
    Digests: "/journeys/nodes/digest",
    Environments: "/workspaces/overview",
    MCP: "/resources/mcp"
  };
  const icons = {
    Email: "envelope",
    SMS: "comment",
    Push: "mobile",
    Inbox: "inbox",
    Chat: "comments",
    Templates: "pen-ruler",
    Variables: "pen-ruler",
    Elemental: "pen-ruler",
    Brands: "pen-ruler",
    Translations: "pen-ruler",
    Routing: "paper-plane",
    Preferences: "users",
    Journeys: "route",
    Broadcasts: "bullhorn",
    Tenants: "building",
    Logs: "chart-simple",
    Webhooks: "chart-simple",
    Lists: "users",
    Users: "users",
    Digests: "route",
    Environments: "briefcase",
    MCP: "toolbox"
  };
  const base = "https://d3gk2c5xim1je2.cloudfront.net/fontawesome/v7.2.0/regular/";
  const names = String(items || "").split(",").map(entry => entry.trim()).filter(Boolean);
  return <div className="cx-tags">
      {names.map(name => {
    const href = routes[name];
    const icon = icons[name];
    const url = icon ? "url(" + base + icon + ".svg)" : null;
    const style = url ? {
      "--cx-tag-icon": url
    } : null;
    if (!href) {
      return <span className="cx-tag" data-icon={icon} style={style} key={name}>
              {name}
            </span>;
    }
    return <a className="cx-tag" data-icon={icon} style={style} href={href} key={name}>
            {name}
          </a>;
  })}
    </div>;
};

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

export const Endpoint = ({method, path, name, href, children, bare}) => {
  const verb = String(method || "").toUpperCase();
  const title = verb + " " + path;
  const label = children || name || path;
  if (bare) {
    return href ? <a href={href}><code>{title}</code></a> : <code>{title}</code>;
  }
  if (!href) {
    return <span className="cx-endpoint" data-method={verb} title={title}>
        <span className="cx-endpoint-label">{label}</span>
        <span className="cx-endpoint-method">{verb}</span>
      </span>;
  }
  return <a className="cx-endpoint" data-method={verb} href={href} title={title}>
      <span className="cx-endpoint-label">{label}</span>
      <span className="cx-endpoint-method">{verb}</span>
    </a>;
};

export const AppLink = ({href, children, name, bare}) => {
  const label = children || name || "Open in Courier";
  if (bare) {
    return <a href={href} target="_blank" rel="noreferrer">{label}</a>;
  }
  return <a className="cx-endpoint" data-kind="app" href={href} target="_blank" rel="noreferrer">
      <span className="cx-endpoint-label">{label}</span>
      <span className="cx-endpoint-method" aria-hidden="true">↗</span>
    </a>;
};

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>;
};

<Tags items="Tenants, Brands, Templates" />

Send from one workspace to many customer orgs, each with its own logo, colors, and notification defaults.

A tenant holds the settings for one customer org. You keep a single set of templates and one integration per channel. Name a tenant on the send, and Courier applies that org's brand and defaults.

## Prerequisites

* <Doc href="/docs/integrations/overview">A connected provider</Doc>
* <Doc href="/docs/design/templates/overview">A published template with its brand enabled</Doc>
* <AppLink href="https://app.courier.com/settings/api-keys">A Courier API key</AppLink>

## Set up a tenant

<Steps>
  <Step title="Create a brand for the customer">
    Open <AppLink href="https://app.courier.com/content/brands">Brands</AppLink> and create one brand per customer org with their logo, colors, and footer. Copy its brand ID.

    One brand serves every template that org receives, so this is the only place their visual identity lives. See <Doc href="/docs/design/brands">brands</Doc> for what a brand controls.
  </Step>

  <Step title="Create the tenant">
    <Endpoint method="PUT" path="/tenants/{tenant_id}" name="Create or replace a Tenant" href="/docs/api-reference/tenants/create-or-replace-a-tenant" /> takes an id you choose, so the same call creates the tenant on signup and updates it on every change after. Point it at the brand you just made.

    <CodeGroup>
      ```javascript Node.js theme={null}
      const tenant = await client.tenants.update('acme-corp', {
        name: 'Acme Corp',
        brand_id: 'bnd_acmecorp',
        properties: { plan: 'enterprise' },
      });
      ```

      ```python Python theme={null}
      tenant = client.tenants.update(
          tenant_id="acme-corp",
          name="Acme Corp",
          brand_id="bnd_acmecorp",
          properties={"plan": "enterprise"},
      )
      ```

      ```bash cURL theme={null}
      curl -X PUT https://api.courier.com/tenants/acme-corp \
        -H "Authorization: Bearer $COURIER_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "name": "Acme Corp",
          "brand_id": "bnd_acmecorp",
          "properties": { "plan": "enterprise" }
        }'
      ```

      ```ruby Ruby theme={null}
      tenant = courier.tenants.update(
        "acme-corp",
        name: "Acme Corp",
        brand_id: "bnd_acmecorp",
        properties: {plan: "enterprise"}
      )
      ```

      ```go Go theme={null}
      tenant, err := client.Tenants.Update(
      	context.TODO(),
      	"acme-corp",
      	courier.TenantUpdateParams{
      		Name:    "Acme Corp",
      		BrandID: courier.String("bnd_acmecorp"),
      		Properties: map[string]any{
      			"plan": "enterprise",
      		},
      	},
      )
      ```

      ```java Java theme={null}
      TenantUpdateParams params = TenantUpdateParams.builder()
          .tenantId("acme-corp")
          .name("Acme Corp")
          .brandId("bnd_acmecorp")
          .properties(TenantUpdateParams.Properties.builder()
              .putAdditionalProperty("plan", JsonValue.from("enterprise"))
              .build())
          .build();
      Tenant tenant = client.tenants().update(params);
      ```

      ```php PHP theme={null}
      $tenant = $client->tenants->update(
        'acme-corp',
        name: 'Acme Corp',
        brandID: 'bnd_acmecorp',
        properties: ['plan' => 'enterprise'],
      );
      ```

      ```csharp C# theme={null}
      var tenant = await client.Tenants.Update(
          "acme-corp",
          new()
          {
              Name = "Acme Corp",
              BrandID = "bnd_acmecorp",
              Properties = new Dictionary<string, JsonElement>()
              {
                  { "plan", JsonSerializer.SerializeToElement("enterprise") }
              },
          }
      );
      ```

      ```bash CLI theme={null}
      courier tenants update \
        --api-key "$COURIER_API_KEY" \
        --tenant-id acme-corp \
        --name "Acme Corp" \
        --brand-id bnd_acmecorp \
        --properties '{"plan":"enterprise"}'
      ```

      ```text MCP theme={null}
      With Courier MCP, create a tenant called Acme Corp and point it at my Acme brand.
      ```
    </CodeGroup>

    Setting `brand_id` on the tenant makes branding automatic. On any send that names this tenant, Courier picks the tenant's brand without a `brand_id` on the request.
  </Step>

  <Step title="Set the org's notification defaults">
    Give the tenant `default_preferences` so a new user in that org starts opted in or out to match the org, before they ever open a preference center. See <Doc href="/docs/tenants/preferences">tenant default preferences</Doc> for the per-topic calls.
  </Step>

  <Step title="Add users to the tenant">
    Put each of the org's users in the tenant so membership drives who receives a fan-out send. Add them one at a time, or in one call with `bulk_add_user_tenants`.

    A user can belong to more than one tenant, which matters for the send shape you choose next.
  </Step>
</Steps>

## Send with the tenant

<Steps>
  <Step title="Notify one user in their org's context">
    Set `context.tenant_id` on the recipient. The message renders with that org's brand, and preference lookups scope to that org.

    <CodeGroup>
      ```javascript Node.js highlight={6} theme={null}
      const { requestId } = await client.send.message({
        message: {
          to: {
            user_id: "user_123",
            context: {
              tenant_id: "acme-corp",
            },
          },
          template: "nt_01kx4h2jdafq8bk9aftxak4b40",
          data: {
            amount: 420.00,
          },
        },
      });
      ```

      ```python Python highlight={6} theme={null}
      response = client.send.message(
          message={
              "to": {
                  "user_id": "user_123",
                  "context": {
                      "tenant_id": "acme-corp",
                  },
              },
              "template": "nt_01kx4h2jdafq8bk9aftxak4b40",
              "data": {
                  "amount": 420.00,
              },
          },
      )
      ```

      ```bash cURL highlight={9} wrap 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": {
              "amount": 420.00
            }
          }
        }'
      ```

      ```ruby Ruby highlight={6} theme={null}
      response = courier.send_.message(
        message: {
          to: {
            user_id: "user_123",
            context: {
              tenant_id: "acme-corp"
            }
          },
          template: "nt_01kx4h2jdafq8bk9aftxak4b40",
          data: {
            amount: 420.00
          }
        }
      )
      ```

      ```go Go highlight={7} 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{
      			"amount": 420.00,
      		},
      	},
      })
      ```

      ```java Java highlight={5} theme={null}
      SendMessageParams params = SendMessageParams.builder()
          .message(SendMessageParams.Message.builder()
              .to(UserRecipient.builder()
                  .userId("user_123")
                  .context(MessageContext.builder().tenantId("acme-corp").build())
                  .build())
              .template("nt_01kx4h2jdafq8bk9aftxak4b40")
              .data(JsonValue.from(java.util.Map.of(
                  "amount", 420.00
              )))
              .build())
          .build();
      SendMessageResponse response = client.send().message(params);
      ```

      ```php PHP highlight={6} theme={null}
      $response = $client->send->message(
        message: [
          'to' => [
            'user_id' => 'user_123',
            'context' => [
              'tenant_id' => 'acme-corp',
            ],
          ],
          'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
          'data' => [
            'amount' => 420.00,
          ],
        ],
      );
      ```

      ```csharp C# highlight={8} 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>()
              {
                  { "amount", JsonSerializer.SerializeToElement(420.00) },
              },
          },
      };

      var response = await client.Send.Message(parameters);
      ```

      ```bash CLI highlight={3} wrap theme={null}
      courier send message \
        --api-key "$COURIER_API_KEY" \
        --message.to '{"user_id": "user_123", "context": {"tenant_id": "acme-corp"}}' \
        --message.template nt_01kx4h2jdafq8bk9aftxak4b40 \
        --message.data '{"amount": 420.00}'
      ```

      ```text MCP theme={null}
      With Courier MCP, send to user_123 with tenant context acme-corp so it uses that org's brand.
      ```
    </CodeGroup>

    The user's own stored profile and preferences still win over the tenant's, so a person who opted out stays opted out.
  </Step>

  <Step title="Notify the whole org">
    Put a `tenant_id` in `to` and Courier looks up the tenant's members and sends to each one.

    <CodeGroup>
      ```javascript Node.js highlight={4} theme={null}
      const { requestId } = await client.send.message({
        message: {
          to: {
            tenant_id: "acme-corp",
          },
          template: "nt_01kx4h2jdafq8bk9aftxak4b40",
          routing: {
            method: "single",
            channels: [
              "inbox",
            ],
          },
        },
      });
      ```

      ```python Python highlight={4} theme={null}
      response = client.send.message(
          message={
              "to": {
                  "tenant_id": "acme-corp",
              },
              "template": "nt_01kx4h2jdafq8bk9aftxak4b40",
              "routing": {
                  "method": "single",
                  "channels": [
                      "inbox",
                  ],
              },
          },
      )
      ```

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

      ```ruby Ruby highlight={4} theme={null}
      response = courier.send_.message(
        message: {
          to: {
            tenant_id: "acme-corp"
          },
          template: "nt_01kx4h2jdafq8bk9aftxak4b40",
          routing: {
            method: "single",
            channels: [
              "inbox"
            ]
          }
        }
      )
      ```

      ```go Go highlight={5} theme={null}
      response, err := client.Send.Message(context.TODO(), courier.SendMessageParams{
      	Message: courier.SendMessageParamsMessage{
      		To: courier.SendMessageParamsMessageToUnion{
      			OfUserRecipient: &shared.UserRecipientParam{
      				TenantID: courier.String("acme-corp"),
      			},
      		},
      		Template: courier.String("nt_01kx4h2jdafq8bk9aftxak4b40"),
      		Routing: courier.SendMessageParamsMessageRouting{
      			Channels: []shared.MessageRoutingChannelUnionParam{
      				{OfString: courier.String("inbox")},
      			},
      			Method: "single",
      		},
      	},
      })
      ```

      ```java Java highlight={3} theme={null}
      SendMessageParams params = SendMessageParams.builder()
          .message(SendMessageParams.Message.builder()
              .to(UserRecipient.builder().tenantId("acme-corp").build())
              .template("nt_01kx4h2jdafq8bk9aftxak4b40")
              .routing(SendMessageParams.Message.Routing.builder()
                  .addChannel("inbox")
                  .method(SendMessageParams.Message.Routing.Method.SINGLE)
                  .build())
              .build())
          .build();
      SendMessageResponse response = client.send().message(params);
      ```

      ```php PHP highlight={4} theme={null}
      $response = $client->send->message(
        message: [
          'to' => [
            'tenant_id' => 'acme-corp',
          ],
          'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
          'routing' => [
            'method' => 'single',
            'channels' => [
              'inbox',
            ],
          ],
        ],
      );
      ```

      ```csharp C# highlight={5} theme={null}
      SendMessageParams parameters = new()
      {
          Message = new()
          {
              To = new UserRecipient { TenantID = "acme-corp" },
              Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
              Routing = new() { Channels = ["inbox"], Method = Send::Method.Single },
          },
      };

      var response = await client.Send.Message(parameters);
      ```

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

      ```text MCP theme={null}
      With Courier MCP, send my nt_01kx4h2jdafq8bk9aftxak4b40 template to every member of acme-corp.
      ```
    </CodeGroup>

    Every targeting shape, including walking a tenant hierarchy up or down, is in <Doc href="/docs/tenants/send">send with tenants</Doc>.
  </Step>

  <Step title="Give one org different content (optional)">
    When an org needs its own wording, not only its own colors, create a tenant template and reference it as `template: "tenant/<template_id>"`. Courier merges its content over the message. See <Doc href="/docs/tenants/templates">tenant templates</Doc>.

    To let the customer write that wording themselves, <Guide href="/docs/guides/let-customers-edit-notifications">put the editor in your app</Guide>.
  </Step>
</Steps>

## Verify

<Steps>
  <Step title="Confirm the brand resolved">
    Send to a user in the tenant, then open the message in <AppLink href="https://app.courier.com/logs">Logs</AppLink>. The output should carry that org's logo and colors, not your workspace default.
  </Step>

  <Step title="Compare two tenants">
    Send the same template to a user in a second tenant and confirm the two rendered messages differ only by brand.
  </Step>

  <Step title="Check the fan-out count">
    Send with `to.tenant_id` and confirm the log shows one message per member of that tenant.
  </Step>
</Steps>

## Troubleshooting

| Symptom                                 | Cause                                                       | Fix                                                                |
| --------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------ |
| `Tenant Context Not Found`              | The user belongs to several tenants and the send named none | Set `context.tenant_id` on the send                                |
| Workspace default brand renders         | Neither the send, the tenant, nor the template set a brand  | Set `brand_id` on the tenant, and enable the brand on the template |
| Inbox shows other orgs' messages        | The client signed in without a `tenantId`                   | Pass `tenantId` at `signIn`                                        |
| A child org is missing a parent setting | The chain is deeper than four levels                        | Flatten the hierarchy to four or fewer                             |

Which isolation layer to reach for, tenant against environment against workspace, is covered in <Doc href="/docs/workspaces/overview#workspace-environment-or-tenant">workspaces</Doc>. The merge rules behind all of the above are in <Doc href="/docs/tenants/context">tenant context</Doc>.
