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

# Message routing and failover

> Choose channels and providers with the routing object, set priority, and fail over on errors.

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

Routing decides which channels and providers a message goes through.

Set a `routing` method and channel list on the send, or save a routing strategy on the template. Courier tries them in order. When one is unavailable, it fails over to the next provider or channel.

## How it works

### method: single vs all

`routing.method` decides how Courier uses the `channels` list:

* **`single`** tries channels in order and stops at the first that works. The rest are its failover.
* **`all`** sends through every channel in the list, so email and push go out together.

<CodeGroup>
  ```javascript Node.js highlight={5-8} theme={null}
  const { requestId } = await courier.send.message({
    message: {
      to: { user_id: "user_123" },
      template: "nt_01kx4h2jdafq8bk9aftxak4b40",
      routing: {
        method: "single",
        channels: ["push", "email"],
      },
    },
  });
  ```

  ```python Python highlight={5-8} theme={null}
  response = client.send.message(
      message={
          "to": {"user_id": "user_123"},
          "template": "nt_01kx4h2jdafq8bk9aftxak4b40",
          "routing": {
              "method": "single",
              "channels": ["push", "email"],
          },
      },
  )
  ```

  ```bash cURL highlight={8-11} 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" },
        "template": "nt_01kx4h2jdafq8bk9aftxak4b40",
        "routing": {
          "method": "single",
          "channels": ["push", "email"]
        }
      }
    }'
  ```

  ```ruby Ruby highlight={5-8} theme={null}
  response = courier.send_.message(
    message: {
      to: { user_id: "user_123" },
      template: "nt_01kx4h2jdafq8bk9aftxak4b40",
      routing: {
        method: "single",
        channels: ["push", "email"]
      }
    }
  )
  ```

  ```go Go highlight={7-13} 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")},
  		},
  		Template: courier.String("nt_01kx4h2jdafq8bk9aftxak4b40"),
  		Routing: courier.SendMessageParamsMessageRouting{
  			Method: string(shared.MessageRoutingMethodSingle),
  			Channels: []shared.MessageRoutingChannelUnionParam{
  				{OfString: courier.String("push")},
  				{OfString: courier.String("email")},
  			},
  		},
  	},
  })
  ```

  ```java Java highlight={5-9} theme={null}
  SendMessageParams params = SendMessageParams.builder()
      .message(SendMessageParams.Message.builder()
          .to(UserRecipient.builder().userId("user_123").build())
          .template("nt_01kx4h2jdafq8bk9aftxak4b40")
          .routing(SendMessageParams.Message.Routing.builder()
              .method(SendMessageParams.Message.Routing.Method.SINGLE)
              .addChannel("push")
              .addChannel("email")
              .build())
          .build())
      .build();
  SendMessageResponse response = client.send().message(params);
  ```

  ```php PHP highlight={5-8} theme={null}
  $response = $client->send->message(
    message: [
      'to' => ['user_id' => 'user_123'],
      'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
      'routing' => [
        'method' => 'single',
        'channels' => ['push', 'email'],
      ],
    ],
  );
  ```

  ```csharp C# highlight={4-8} theme={null}
  SendMessageParams parameters = new()
  {
      Message = new()
      {
          To = new UserRecipient { UserID = "user_123" },
          Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
          Routing = new Routing { Method = Method.Single, Channels = ["push", "email"] },
      },
  };

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

  ```bash CLI highlight={5} theme={null}
  courier send message \
    --api-key "$COURIER_API_KEY" \
    --message.to '{"user_id": "user_123"}' \
    --message.template nt_01kx4h2jdafq8bk9aftxak4b40 \
    --message.routing '{"method": "single", "channels": ["push", "email"]}'
  ```

  ```text MCP theme={null}
  With Courier MCP, send my nt_01kx4h2jdafq8bk9aftxak4b40 template to user_123, trying push first and falling back to email.
  ```
</CodeGroup>

If you omit `routing`, Courier uses the template's routing strategy, or a default of `{ "method": "single", "channels": ["email"] }`.

### What goes in the channels list

Each entry in `channels` is one of:

* A **channel key**: `email`, `sms`, `push`, `inbox`, `direct_message`.
* A **provider key** to target one provider directly: `twilio`, `sendgrid`. See <Doc href="/docs/send/send-to-a-provider">Send to a specific provider</Doc>.
* A nested **routing object**: a group of channels with its own `method`. Use it to express "try push, then fall back to email or SMS together."

### Channel priority

Within a channel, Courier tries providers in priority order. The order you set in the channel's settings comes first, then any other configured providers. Each provider can carry an `if` condition. Courier skips a provider that is not configured or whose condition fails. The channel's own `routing_method` (`single` or `all`) decides whether it delivers through the first working provider or all of them.

### Reaching iOS and Android in one send

APNs and FCM are two providers **inside the one `push` channel**, and a channel's `routing_method` defaults to `single`. Courier delivers through the first provider that works and stops, so a user with both an iPhone and an Android tablet gets the notification on one device.

Set that channel's `routing_method` to `all`. It goes in `message.channels`, keyed by channel name, not in the `routing` object:

<CodeGroup>
  ```javascript Node.js highlight={7} theme={null}
  const { requestId } = await courier.send.message({
    message: {
      to: { user_id: "user_123" },
      template: "nt_01kx4h2jdafq8bk9aftxak4b40",
      routing: { method: "single", channels: ["push"] },
      channels: {
        push: { routing_method: "all" },
      },
    },
  });
  ```

  ```python Python highlight={7} theme={null}
  response = client.send.message(
      message={
          "to": {"user_id": "user_123"},
          "template": "nt_01kx4h2jdafq8bk9aftxak4b40",
          "routing": {"method": "single", "channels": ["push"]},
          "channels": {
              "push": {"routing_method": "all"},
          },
      },
  )
  ```

  ```bash cURL highlight={10} 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" },
        "template": "nt_01kx4h2jdafq8bk9aftxak4b40",
        "routing": { "method": "single", "channels": ["push"] },
        "channels": {
          "push": { "routing_method": "all" }
        }
      }
    }'
  ```

  ```ruby Ruby highlight={7} theme={null}
  response = courier.send_.message(
    message: {
      to: { user_id: "user_123" },
      template: "nt_01kx4h2jdafq8bk9aftxak4b40",
      routing: { method: "single", channels: ["push"] },
      channels: {
        push: { routing_method: "all" }
      }
    }
  )
  ```

  ```go Go highlight={13} 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")},
  		},
  		Template: courier.String("nt_01kx4h2jdafq8bk9aftxak4b40"),
  		Routing: courier.SendMessageParamsMessageRouting{
  			Method:   string(shared.MessageRoutingMethodSingle),
  			Channels: []shared.MessageRoutingChannelUnionParam{{OfString: courier.String("push")}},
  		},
  		Channels: shared.MessageChannelsParam{
  			"push": shared.ChannelParam{
  				RoutingMethod: shared.ChannelRoutingMethodAll,
  			},
  		},
  	},
  })
  ```

  ```java Java highlight={11} theme={null}
  SendMessageParams params = SendMessageParams.builder()
      .message(SendMessageParams.Message.builder()
          .to(UserRecipient.builder().userId("user_123").build())
          .template("nt_01kx4h2jdafq8bk9aftxak4b40")
          .routing(SendMessageParams.Message.Routing.builder()
              .method(SendMessageParams.Message.Routing.Method.SINGLE)
              .addChannel("push")
              .build())
          .channels(MessageChannels.builder()
              .putAdditionalProperty("push", JsonValue.from(java.util.Map.of(
                  "routing_method", "all")))
              .build())
          .build())
      .build();
  SendMessageResponse response = client.send().message(params);
  ```

  ```php PHP highlight={7} theme={null}
  $response = $client->send->message(
    message: [
      'to' => ['user_id' => 'user_123'],
      'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
      'routing' => ['method' => 'single', 'channels' => ['push']],
      'channels' => [
        'push' => ['routing_method' => 'all'],
      ],
    ],
  );
  ```

  ```csharp C# highlight={10} theme={null}
  SendMessageParams parameters = new()
  {
      Message = new()
      {
          To = new UserRecipient { UserID = "user_123" },
          Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
          Routing = new Routing { Method = Method.Single, Channels = ["push"] },
          Channels = new Dictionary<string, Channel>()
          {
              { "push", new() { RoutingMethod = RoutingMethod.All } },
          },
      },
  };

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

  ```bash CLI highlight={6} theme={null}
  courier send message \
    --api-key "$COURIER_API_KEY" \
    --message.to '{"user_id": "user_123"}' \
    --message.template nt_01kx4h2jdafq8bk9aftxak4b40 \
    --message.routing '{"method": "single", "channels": ["push"]}' \
    --message.channels '{"push": {"routing_method": "all"}}'
  ```

  ```text MCP theme={null}
  With Courier MCP, send my nt_01kx4h2jdafq8bk9aftxak4b40 template to user_123 on push, reaching every device rather than stopping at the first.
  ```
</CodeGroup>

`routing.method` chooses between channels. A channel's `routing_method` chooses between the providers inside it. A user with only an iPhone is unaffected either way, because FCM is skipped without a token.

<Warning>
  **Both providers have to be on the channel first.**<br />
  A channel delivers through the providers attached to it, and `routing_method` only decides how many of them it uses. Install each provider under <AppLink href="https://app.courier.com/integrations">Integrations</AppLink>, then open the template's **routing selector** in the <AppLink href="https://app.courier.com/content/templates">designer</AppLink> and add both APNs and Firebase FCM to the push channel. An older template routes only through the providers on its own channel, so one you skipped there never becomes a candidate.
</Warning>

### Failover

Failover is automatic and works at two levels. Within a channel, if the first provider fails, Courier tries the next configured provider. Across channels, if a whole channel fails, Courier moves to the next channel in the list. A channel fails when no provider worked on a `single` route, or when the user has no address for it. Courier retries transient provider errors before marking a provider failed. The message log records each attempt.

### Routing configuration and strategies

Rather than repeat a `routing` object on every send, save a **routing strategy** and attach it to a template. A strategy (ID prefixed `rs_`) holds a reusable `method`, channel order, and provider order, so content and routing change independently.

### Delivery pipeline resilience

The pipeline honors timeouts so a slow provider does not block the send. A **message-level timeout** is available on every plan. **Per-provider and per-channel custom timeouts** (`message.timeout.provider`, `message.timeout.channel`, or a `timeout` on a specific channel or provider) are set the same way. Failover itself is never gated.

## Limits & behavior

* **Omitted routing falls back to single email.** With no `routing` and no template strategy, Courier routes `single` to `email`.
* **Message-level and custom per-provider or per-channel timeouts both apply.** Failover is not gated.
* **Failover is automatic.** Courier tries the next provider, then the next channel, retrying transient errors, and logs each attempt.

## FAQ

<AccordionGroup>
  <Accordion title="What is the difference between single and all?">
    `single` sends through the first channel that works (the rest are failover). `all` sends through every channel in the list at once.
  </Accordion>

  <Accordion title="How does Courier choose which provider sends?">
    Within a channel it uses your configured provider order first, then any other configured providers. It skips providers whose condition fails or that are not set up. If the first fails, it fails over to the next.
  </Accordion>

  <Accordion title="Do I need a routing object on every send?">
    Attach a routing strategy to the template and omit `routing` on the send to reuse it. Set `routing` inline only when you want to override the strategy for one message.
  </Accordion>

  <Accordion title="How do I send one push to both iOS and Android?">
    Set the push channel's `routing_method` to `all` in `message.channels`, and make sure both providers are on that channel in the template's routing selector. APNs and FCM are two providers in the one `push` channel, and the default `single` treats them as failover, so a user with devices on both platforms only gets one. See [reaching iOS and Android in one send](#reaching-ios-and-android-in-one-send).
  </Accordion>

  <Accordion title="Is failover a paid feature?">
    Failover itself is never gated, and both message-level and custom per-provider or per-channel timeouts apply.
  </Accordion>
</AccordionGroup>
