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

# Build a custom provider for Courier

> Route messages to your own webhook so an in-house channel works like any provider.

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

## Prerequisites

* An HTTPS endpoint you control, ready to receive Courier's webhook
* The authentication your endpoint expects, if any

## Setup

Open the <AppLink href="https://app.courier.com/integrations/catalog/custom">Custom Provider integration</AppLink>. Enter your webhook HTTP address and choose an authentication model.

<Frame>
  <img src="https://mintcdn.com/courier-4f1f25dc/fN1MbwzHtG3Vhos5/assets/custom-provider.webp?fit=max&auto=format&n=fN1MbwzHtG3Vhos5&q=85&s=fda6a402412de38293abe5fb9c39939a" alt="Custom Provider" width="1266" height="1226" data-path="assets/custom-provider.webp" />
</Frame>

You can now add the Custom Provider to any Push channel. Add a Push channel, then open the Channel Settings modal.

<Frame>
  <img src="https://mintcdn.com/courier-4f1f25dc/fN1MbwzHtG3Vhos5/assets/custom-provider-settings.webp?fit=max&auto=format&n=fN1MbwzHtG3Vhos5&q=85&s=67496b358769a77e5b15506c3a8e0ae9" alt="Custom Provider Settings" width="714" height="344" data-path="assets/custom-provider-settings.webp" />
</Frame>

"Custom" appears in the list of installed providers.

<Frame>
  <img src="https://mintcdn.com/courier-4f1f25dc/fN1MbwzHtG3Vhos5/assets/custom-installed-provider.webp?fit=max&auto=format&n=fN1MbwzHtG3Vhos5&q=85&s=f96895090a1fee950bf63c2bda17c195" alt="Installed Custom Provider" width="1460" height="432" data-path="assets/custom-installed-provider.webp" />
</Frame>

Add a title and blocks in the designer. Courier sends them to your webhook as plain text and as an array of blocks.

<Frame>
  <img src="https://mintcdn.com/courier-4f1f25dc/fN1MbwzHtG3Vhos5/assets/custom-provider-designer.webp?fit=max&auto=format&n=fN1MbwzHtG3Vhos5&q=85&s=46cd8db590f00cd0317d3af0ec46bb59" alt="Custom Provider Designer" width="916" height="482" data-path="assets/custom-provider-designer.webp" />
</Frame>

When the message sends, your webhook receives a payload like this:

```ts theme={null}
interface TextBlock {
  type: "text";
  text: string;
}

interface ActionBlock {
  type: "action";
  url: string;
  text: string;
}

interface PushMessage {
  type: "push";
  data: {
  	messageId: string;
    content: {
      title: string;
      body: string;
      blocks: Array<ActionBlock | TextBlock>
    }
  }
}
```

```json theme={null}
{
  "type": "push",
  "data": {
    "messageId": "1-6140e057-2749378a31c6026f3dab823f",
    "content": {
      "blocks": [
        {
          "text": "My Body",
          "type": "text"
        },
        {
          "text": "Click Here",
          "url": "https://example.execute-api.us-east-1.amazonaws.com/dev/r/TRACKING_ID",
          "type": "action"
        }
      ],
      "body": "My Body\nClick Here: https://example.execute-api.us-east-1.amazonaws.com/dev/r/TRACKING_ID",
      "title": "My Title"
    }
  }
}
```

## Profile requirements

No profile data is required. Courier posts the rendered message to your endpoint, and your endpoint decides how to address the recipient.

## Overrides

<Doc href="/docs/send/overrides#how-overrides-work">How overrides work</Doc> covers the two levels and which one wins.

An override changes what Courier sends to your custom provider. You can override the `body`, `headers`, `method`, and `url` fields. `body` is deep-merged into the request, so fields you leave out are still sent.

<CodeGroup>
  ```javascript Node.js highlight={8-15} theme={null}
  const { requestId } = await courier.send.message({
    message: {
      template: "nt_01kx4h2jdafq8bk9aftxak4b40",
      to: {
        email: "sarah@acme-corp.com",
      },
      providers: {
        custom: {
          override: {
            body: {},
            headers: {},
            method: "POST",
            url: "https://your-endpoint.example.com/webhook",
          },
        },
      },
    },
  });
  ```

  ```python Python highlight={8-15} theme={null}
  response = client.send.message(
      message={
          "template": "nt_01kx4h2jdafq8bk9aftxak4b40",
          "to": {
              "email": "sarah@acme-corp.com",
          },
          "providers": {
              "custom": {
                  "override": {
                      "body": {},
                      "headers": {},
                      "method": "POST",
                      "url": "https://your-endpoint.example.com/webhook",
                  },
              },
          },
      },
  )
  ```

  ```bash cURL highlight={11-18} wrap theme={null}
  curl -X POST https://api.courier.com/send \
    -H "Authorization: Bearer $COURIER_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "message": {
        "template": "nt_01kx4h2jdafq8bk9aftxak4b40",
        "to": {
          "email": "sarah@acme-corp.com"
        },
        "providers": {
          "custom": {
            "override": {
              "body": {},
              "headers": {},
              "method": "POST",
              "url": "https://your-endpoint.example.com/webhook"
            }
          }
        }
      }
    }'
  ```

  ```ruby Ruby highlight={8-15} theme={null}
  response = courier.send_.message(
    message: {
      template: "nt_01kx4h2jdafq8bk9aftxak4b40",
      to: {
        email: "sarah@acme-corp.com"
      },
      providers: {
        custom: {
          override: {
            body: {},
            headers: {},
            method: "POST",
            url: "https://your-endpoint.example.com/webhook"
          }
        }
      }
    }
  )
  ```

  ```go Go highlight={10-17} theme={null}
  response, err := client.Send.Message(context.TODO(), courier.SendMessageParams{
  	Message: courier.SendMessageParamsMessage{
  		To: courier.SendMessageParamsMessageToUnion{
  			OfUserRecipient: &shared.UserRecipientParam{
  				Email: courier.String("sarah@acme-corp.com"),
  			},
  		},
  		Template: courier.String("nt_01kx4h2jdafq8bk9aftxak4b40"),
  		Providers: shared.MessageProvidersParam{
  			"custom": shared.MessageProvidersTypeParam{
  				Override: map[string]any{
  					"body": map[string]any{},
  					"headers": map[string]any{},
  					"method": "POST",
  					"url": "https://your-endpoint.example.com/webhook",
  				},
  			},
  		},
  	},
  })
  ```

  ```java Java highlight={6-11} theme={null}
  SendMessageParams params = SendMessageParams.builder()
      .message(SendMessageParams.Message.builder()
          .to(UserRecipient.builder().email("sarah@acme-corp.com").build())
          .template("nt_01kx4h2jdafq8bk9aftxak4b40")
          .providers(MessageProviders.builder()
              .putAdditionalProperty("custom", JsonValue.from(java.util.Map.of("override", java.util.Map.of(
                      "body", java.util.Map.of(),
                      "headers", java.util.Map.of(),
                      "method", "POST",
                      "url", "https://your-endpoint.example.com/webhook"
                  ))))
              .build())
          .build())
      .build();
  SendMessageResponse response = client.send().message(params);
  ```

  ```php PHP highlight={8-15} theme={null}
  $response = $client->send->message(
    message: [
      'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
      'to' => [
        'email' => 'sarah@acme-corp.com',
      ],
      'providers' => [
        'custom' => [
          'override' => [
            'body' => [],
            'headers' => [],
            'method' => 'POST',
            'url' => 'https://your-endpoint.example.com/webhook',
          ],
        ],
      ],
    ],
  );
  ```

  ```csharp C# highlight={9-24} theme={null}
  SendMessageParams parameters = new()
  {
      Message = new()
      {
          To = new UserRecipient { Email = "sarah@acme-corp.com" },
          Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
          Providers = new Dictionary<string, MessageProvidersType>()
          {
              {
                  "custom",
                  new()
                  {
                      Override = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
                          """
                          {
                            "body": {},
                            "headers": {},
                            "method": "POST",
                            "url": "https://your-endpoint.example.com/webhook"
                          }
                          """
                      ),
                  }
              },
          },
      },
  };

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

  ```bash CLI highlight={5} wrap theme={null}
  courier send message \
    --api-key "$COURIER_API_KEY" \
    --message.to '{"email": "sarah@acme-corp.com"}' \
    --message.template nt_01kx4h2jdafq8bk9aftxak4b40 \
    --message.providers '{"custom": {"override": {"body": {}, "headers": {}, "method": "POST", "url": "https://your-endpoint.example.com/webhook"}}}'
  ```

  ```text MCP theme={null}
  With Courier MCP, send my template to sarah@acme-corp.com and override my custom provider's URL and body.
  ```
</CodeGroup>

## Provider details

```text theme={null}
custom
```

Courier recommends routing to the channel. Naming this key in `routing.channels` instead is supported, and sends through just this provider.

<Card title="Send to a specific provider" icon="bullseye-arrow" href="/docs/send/send-to-a-provider" horizontal arrow="true">
  When that is worth doing, and what you give up: failover, channel priority, and providers you add later.
</Card>
