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

# Send to a provider

> Send through one specific provider by naming it in routing instead of a channel.

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

Courier recommends routing to a channel. Naming a provider key in `routing.channels` instead is supported, and sends through that one provider.

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

  ```python Python highlight={10} theme={null}
  response = client.send.message(
      message={
          "to": {
              "user_id": "user_123",
          },
          "template": "nt_01kx4h2jdafq8bk9aftxak4b40",
          "routing": {
              "method": "single",
              "channels": [
                  "some-provider-key",
              ],
          },
      },
  )
  ```

  ```bash cURL highlight={13} 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"
        },
        "template": "nt_01kx4h2jdafq8bk9aftxak4b40",
        "routing": {
          "method": "single",
          "channels": [
            "some-provider-key"
          ]
        }
      }
    }'
  ```

  ```ruby Ruby highlight={10} theme={null}
  response = courier.send_.message(
    message: {
      to: {
        user_id: "user_123"
      },
      template: "nt_01kx4h2jdafq8bk9aftxak4b40",
      routing: {
        method: "single",
        channels: [
          "some-provider-key"
        ]
      }
    }
  )
  ```

  ```go Go highlight={11} 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{
  			Channels: []shared.MessageRoutingChannelUnionParam{
  				{OfString: courier.String("some-provider-key")},
  			},
  			Method: "single",
  		},
  	},
  })
  ```

  ```java Java highlight={6} 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()
              .addChannel("some-provider-key")
              .method(SendMessageParams.Message.Routing.Method.SINGLE)
              .build())
          .build())
      .build();
  SendMessageResponse response = client.send().message(params);
  ```

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

  ```csharp C# highlight={7} theme={null}
  SendMessageParams parameters = new()
  {
      Message = new()
      {
          To = new UserRecipient { UserID = "user_123" },
          Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
          Routing = new() { Channels = ["some-provider-key"], Method = Send::Method.Single },
      },
  };

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

  ```bash CLI highlight={5} wrap 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": ["some-provider-key"]}'
  ```

  ```text MCP theme={null}
  With Courier MCP, send my nt_01kx4h2jdafq8bk9aftxak4b40 template to user_123 through some-provider-key only.
  ```
</CodeGroup>

## Route to the channel first

Routing to a channel is what Courier recommends. Attach your providers to it, address the recipient, and route to the channel by name.

Courier then tries that channel's providers in <Doc href="/docs/send/routing#channel-priority">priority order</Doc> and moves to the next one when a send fails. A provider you connect later joins the order without a code change.

Pin a provider only for the cases below.

## When to pin one

* Smoke-testing a provider you just connected, before it carries real traffic.
* Reproducing one provider's behavior while you debug a delivery.
* Splitting traffic across two providers on the same channel, such as a transactional sender and a bulk sender.

## What pinning costs

| You give up               | Because                                                              |
| :------------------------ | :------------------------------------------------------------------- |
| **Failover**              | The list holds one provider, so there is nothing to fall back to.    |
| **Channel priority**      | The channel's provider order no longer decides anything.             |
| **Providers added later** | The key names one provider, and a new one never becomes a candidate. |

<Warning>
  **A provider key selects, it does not install.** Connect the provider under <AppLink href="https://app.courier.com/integrations">Integrations</AppLink>, then add it to the channel in the template's routing selector. A channel delivers only through the providers attached to it.
</Warning>

## Overrides need no pin

A <Doc href="/docs/send/overrides#how-overrides-work">provider override</Doc> is keyed by provider, and Courier applies it when that provider runs. Route to the channel and the override still lands.

<CodeGroup>
  ```javascript Node.js highlight={10} theme={null}
  const { requestId } = await courier.send.message({
    message: {
      to: {
        user_id: "user_123",
      },
      template: "nt_01kx4h2jdafq8bk9aftxak4b40",
      routing: {
        method: "single",
        channels: [
          "push",
        ],
      },
      providers: {
        apn: {
          override: {
            body: {
              badge: 99,
            },
          },
        },
      },
    },
  });
  ```

  ```python Python highlight={10} theme={null}
  response = client.send.message(
      message={
          "to": {
              "user_id": "user_123",
          },
          "template": "nt_01kx4h2jdafq8bk9aftxak4b40",
          "routing": {
              "method": "single",
              "channels": [
                  "push",
              ],
          },
          "providers": {
              "apn": {
                  "override": {
                      "body": {
                          "badge": 99,
                      },
                  },
              },
          },
      },
  )
  ```

  ```bash cURL highlight={13} 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"
        },
        "template": "nt_01kx4h2jdafq8bk9aftxak4b40",
        "routing": {
          "method": "single",
          "channels": [
            "push"
          ]
        },
        "providers": {
          "apn": {
            "override": {
              "body": {
                "badge": 99
              }
            }
          }
        }
      }
    }'
  ```

  ```ruby Ruby highlight={10} theme={null}
  response = courier.send_.message(
    message: {
      to: {
        user_id: "user_123"
      },
      template: "nt_01kx4h2jdafq8bk9aftxak4b40",
      routing: {
        method: "single",
        channels: [
          "push"
        ]
      },
      providers: {
        apn: {
          override: {
            body: {
              badge: 99
            }
          }
        }
      }
    }
  )
  ```

  ```go Go highlight={11} 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{
  			Channels: []shared.MessageRoutingChannelUnionParam{
  				{OfString: courier.String("push")},
  			},
  			Method: "single",
  		},
  		Providers: shared.MessageProvidersParam{
  			"apn": shared.MessageProvidersTypeParam{
  				Override: map[string]any{
  					"body": map[string]any{
  						"badge": 99,
  					},
  				},
  			},
  		},
  	},
  })
  ```

  ```java Java highlight={6} 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()
              .addChannel("push")
              .method(SendMessageParams.Message.Routing.Method.SINGLE)
              .build())
          .providers(MessageProviders.builder()
              .putAdditionalProperty("apn", JsonValue.from(java.util.Map.of("override", java.util.Map.of(
                      "body", java.util.Map.of("badge", 99)
                  ))))
              .build())
          .build())
      .build();
  SendMessageResponse response = client.send().message(params);
  ```

  ```php PHP highlight={10} theme={null}
  $response = $client->send->message(
    message: [
      'to' => [
        'user_id' => 'user_123',
      ],
      'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
      'routing' => [
        'method' => 'single',
        'channels' => [
          'push',
        ],
      ],
      'providers' => [
        'apn' => [
          'override' => [
            'body' => [
              'badge' => 99,
            ],
          ],
        ],
      ],
    ],
  );
  ```

  ```csharp C# highlight={7} theme={null}
  SendMessageParams parameters = new()
  {
      Message = new()
      {
          To = new UserRecipient { UserID = "user_123" },
          Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
          Routing = new() { Channels = ["push"], Method = Send::Method.Single },
          Providers = new Dictionary<string, MessageProvidersType>()
          {
              {
                  "apn",
                  new()
                  {
                      Override = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
                          """{"body": {"badge": 99}}"""
                      ),
                  }
              },
          },
      },
  };

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

  ```bash CLI highlight={5} wrap 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.providers '{"apn": {"override": {"body": {"badge": 99}}}}'
  ```

  ```text MCP theme={null}
  With Courier MCP, send my nt_01kx4h2jdafq8bk9aftxak4b40 template to user_123 on push with a badge count of 99.
  ```
</CodeGroup>

APNs gets the badge. Firebase FCM, on the same channel, is untouched.

## Find a provider key

Every provider page ends with a **Provider key** section carrying the exact string, and <Doc href="/docs/integrations/overview">Integrations</Doc> lists them all. The key also names each attempt in <AppLink href="https://app.courier.com/logs">Logs</AppLink>.

## Limits & behavior

* **A provider key is a supported value, not a workaround.** Courier recommends a channel key, and accepts either in the same list.
* **A pinned provider has no failover.** Channel priority and the next-provider fallback both stop applying.
* **The provider must already be on the template's channel.** A key in `routing.channels` selects, it does not attach.
* **`providers.<key>.override` works without a pin.** Route to the channel and the override applies when that provider runs.
