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

# Courier agent skills

> Install Courier Skills so your AI coding agent follows channel rules and verified SDK shapes.

Courier Skills is structured guidance AI coding agents follow when building notifications.

Install it once. Your agent gets channel-specific delivery rules, reliability patterns, and code examples for the whole Courier platform.

Works with Cursor, Claude Code, Codex, Windsurf, Cline, and any tool that supports agent skills.

## Installation

<Tabs>
  <Tab title="Any assistant (recommended)">
    ```bash theme={null}
    npx skills add trycourier/courier-skills
    ```
  </Tab>

  <Tab title="Claude Code (plugin)">
    The plugin self-updates and ships the Courier docs MCP server, so the agent can look things up with no extra setup:

    ```
    /plugin marketplace add trycourier/courier-skills
    /plugin install courier@courier-skills
    ```

    Run `/plugin update courier@courier-skills` to pick up changes.
  </Tab>

  <Tab title="Manual clone">
    For any tool that reads a skills directory, clone [courier-skills on GitHub](https://github.com/trycourier/courier-skills) and copy the skill into your assistant's skills folder:

    ```bash theme={null}
    git clone https://github.com/trycourier/courier-skills.git /tmp/courier-skills
    cp -R /tmp/courier-skills/skills/courier ~/.cursor/skills/   # or ~/.claude/skills/
    ```
  </Tab>
</Tabs>

Agents discover the skill from the `name` and `description` frontmatter in `SKILL.md`. There is exactly one, named `courier`, and nothing else to configure.

## What your agent learns

| Category                    | Coverage                                                                                                                                                                                                |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **7 Channels**              | Email, SMS, push, in-app inbox, Slack, Microsoft Teams, WhatsApp. Each with deliverability rules, provider notes, and code examples                                                                     |
| **5 Transactional Types**   | Authentication (OTP, password reset), orders, billing, appointments, account notifications. Timing requirements, security rules, and templates                                                          |
| **6 Growth Types**          | Onboarding, adoption, engagement, re-engagement, referral, campaigns. Which Courier primitive each one needs, so frequency capping and digest scheduling are not rebuilt in your own code               |
| **20 Cross-Cutting Guides** | Multi-channel routing, preferences, reliability, batching, throttling, scheduling, journeys, templates, brands, tenants, audiences, bulk, providers, webhooks, CLI and MCP usage, and reusable patterns |

## How it works

Agents read a routing file (`SKILL.md`) first. It points them at only the 1-2 files relevant to the task.

Each resource file has the same structure:

1. **Quick Reference** at the top with hard rules, common mistakes, and copy-paste templates
2. **Detailed guidance** with explanations, examples, and edge cases
3. **Related links** to other files

So agents read only what they need, not the entire skill.

### Example: agent building an OTP flow

1. Agent reads `SKILL.md`, finds the routing table
2. Routing table says: OTP/2FA -> read `transactional.md` + `sms.md`
3. Agent reads both files and gets: SMS first with email as the fallback, `routing.method: "single"` so one code never goes out twice, an idempotency key, and a suggested cap of five OTP requests an hour
4. Agent generates code that follows every constraint

## Check that it worked

Skills load silently, so the quickest confirmation is to ask for something the skill has an opinion about:

> Add a one-time passcode flow to this project.

With the skill loaded, the agent reaches for SMS first with email as the fallback, routes on a single channel rather than sending to every one, and adds an idempotency key. If it fans the code out to every channel at once, or invents an endpoint, the skill is not being read.

If nothing changes, check that the skill landed in the directory your tool actually reads, then start a new session. Most agents pick skills up at session start.

## Code examples

The skill generates the send in every language Courier ships an SDK for. A
transactional send always carries an idempotency key, so a retry after a network
failure returns the original result instead of sending twice:

<CodeGroup>
  ```javascript Node.js highlight={7} theme={null}
  const { requestId } = await client.send.message({
    message: {
      to: { user_id: 'user_123' },
      template: 'nt_01kx4h2jdafq8bk9aftxak4b40',
      data: { order_id: 'ORD-9042' },
    },
    'Idempotency-Key': 'order-confirmation-ORD-9042',
  });
  ```

  ```python Python highlight={7} theme={null}
  response = client.send.message(
      message={
          "to": {"user_id": "user_123"},
          "template": "nt_01kx4h2jdafq8bk9aftxak4b40",
          "data": {"order_id": "ORD-9042"},
      },
      idempotency_key="order-confirmation-ORD-9042",
  )
  ```

  ```bash cURL highlight={4} theme={null}
  curl -X POST https://api.courier.com/send \
    -H "Authorization: Bearer $COURIER_API_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: order-confirmation-ORD-9042" \
    -d '{
      "message": {
        "to": { "user_id": "user_123" },
        "template": "nt_01kx4h2jdafq8bk9aftxak4b40",
        "data": { "order_id": "ORD-9042" }
      }
    }'
  ```

  ```ruby Ruby highlight={7} theme={null}
  response = courier.send_.message(
    message: {
      to: { user_id: "user_123" },
      template: "nt_01kx4h2jdafq8bk9aftxak4b40",
      data: { order_id: "ORD-9042" }
    },
    idempotency_key: "order-confirmation-ORD-9042"
  )
  ```

  ```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"),
  		Data: map[string]any{
  			"order_id": "ORD-9042",
  		},
  	},
  	IdempotencyKey: courier.String("order-confirmation-ORD-9042"),
  })
  ```

  ```java Java highlight={7} theme={null}
  SendMessageParams params = SendMessageParams.builder()
      .message(SendMessageParams.Message.builder()
          .to(JsonValue.from(java.util.Map.of("user_id", "user_123")))
          .template("nt_01kx4h2jdafq8bk9aftxak4b40")
          .data(JsonValue.from(java.util.Map.of("order_id", "ORD-9042")))
          .build())
      .idempotencyKey("order-confirmation-ORD-9042")
      .build();
  client.send().message(params);
  ```

  ```php PHP highlight={7} theme={null}
  $response = $client->send->message(
    message: [
      'to' => ['user_id' => 'user_123'],
      'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
      'data' => ['order_id' => 'ORD-9042'],
    ],
    idempotencyKey: 'order-confirmation-ORD-9042',
  );
  ```

  ```csharp C# highlight={12} theme={null}
  SendMessageParams parameters = new()
  {
      Message = new()
      {
          To = new UserRecipient { UserID = "user_123" },
          Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
          Data = new Dictionary<string, JsonElement>()
          {
              { "order_id", JsonSerializer.SerializeToElement("ORD-9042") },
          },
      },
      IdempotencyKey = "order-confirmation-ORD-9042",
  };

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

  ```bash CLI highlight={3} theme={null}
  courier send message \
    --api-key "$COURIER_API_KEY" \
    --idempotency-key "order-confirmation-ORD-9042" \
    --message '{"to":{"user_id":"user_123"},"template":"nt_01kx4h2jdafq8bk9aftxak4b40","data":{"order_id":"ORD-9042"}}'
  ```

  ```text MCP theme={null}
  With Courier MCP, send my nt_01kx4h2jdafq8bk9aftxak4b40 template to user_123 with order ORD-9042.
  ```
</CodeGroup>

## Universal rules

The skill enforces these constraints across all generated code:

* Never send promotional content in transactional notifications
* Never batch or delay OTP, password reset, or security alerts
* Never batch, delay, or fan out a code that is meant to reach one device
* Always use idempotency keys for transactional sends
* Always express quiet hours as a native delivery window rather than queueing in your own code
* Always use `method: "single"` unless the notification warrants all channels
