> ## 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 CLI recipes

> One command per task: send, profiles, messages, delivery debugging, lists, bulk, templates, tenants.

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

One block per task. <Doc href="/docs/resources/cli">Install the CLI</Doc> first, and export `COURIER_API_KEY`.

```bash theme={null}
export COURIER_API_KEY=YOUR_COURIER_API_KEY
```

## Send a notification

```bash theme={null}
courier send message \
  --message.to '{"user_id": "user_123"}' \
  --message.template "order-confirmation" \
  --message.data '{"orderId": "ORD-456"}'
```

Only a published template sends. To send without one, pass `--message.content` with a JSON
object holding `title` and `body`, instead of `--message.template`.

## Manage profiles

```bash theme={null}
courier profiles create user_123 --profile '{"email": "sarah@acme-corp.com"}'
courier profiles retrieve user_123
courier profiles update user_123 --patch '[{"op":"replace","path":"/email","value":"sarah@acme-corp.com"}]'
courier profiles delete user_123
```

`create` merges into an existing profile. `replace` overwrites it, so include every field you
want to keep.

## Check a message

```bash theme={null}
courier messages retrieve <message_id>
courier messages list --recipient "user_123" --format json
```

## Debug a delivery

```bash theme={null}
courier messages history <message_id>
courier messages content <message_id>
```

**`history` is the one to reach for first.** It returns each event in the send with the status
reason, which is what tells you whether a message was never routed, was rejected by the
provider, or was delivered and never opened. `content` shows what actually rendered, which
separates a template bug from a delivery one.

```bash theme={null}
courier messages resend <message_id>
courier messages cancel <message_id>
```

## Send to a list

```bash theme={null}
courier lists retrieve my-list
courier profiles:lists subscribe user_123 --lists '["my-list"]'
courier send message --message.to '{"list_id": "my-list"}' --message.template "weekly-digest"
```

## Send in bulk

Four calls in order. The job holds the recipients, and nothing sends until you run it.

```bash theme={null}
courier bulk create-job --message '{"template": "product-update"}'
courier bulk add-users <job_id> --users '[{"recipient":"user_123"},{"recipient":"user_456"}]'
courier bulk run-job <job_id>
courier bulk retrieve-job <job_id>
```

Add users in batches rather than one call per recipient. `retrieve-job` reports progress, and
`courier bulk list-users <job_id>` shows the per-recipient outcome once it has run.

## Manage templates

```bash theme={null}
courier notifications list
courier notifications retrieve <template_id>
courier notifications put-content <template_id> --content '{"version":"2022-01-01","elements":[]}'
courier notifications publish <template_id>
```

**A write leaves a draft.** `publish` is what makes it live, and a send resolves the published
version, so a content change nothing published reaches no recipient.

## Manage tenants

```bash theme={null}
courier tenants list
courier tenants update acme-corp --name "Acme Corp"
courier tenants list-users acme-corp
```

## Manage preferences

```bash theme={null}
courier users:preferences retrieve user_123
courier users:preferences update-or-create-topic user_123 <topic_id> \
  --status "OPTED_IN" --hasCustomRouting true --customRouting '["email"]'
```

Send `hasCustomRouting` and the full `customRouting` array together, because the write
replaces the topic's routing rather than merging into it.

## Global flags

Every command takes these.

| Flag                                   | Use it to                                                                          |
| -------------------------------------- | ---------------------------------------------------------------------------------- |
| `--api-key`                            | Override `COURIER_API_KEY` for one call.                                           |
| `--format`                             | Set the output format, `json` for anything a script reads.                         |
| `--raw-output`                         | Print the response body with no wrapping.                                          |
| `--transform`                          | Reshape the response before it prints.                                             |
| `--format-error` / `--transform-error` | The same two, applied to an error response.                                        |
| `--base-url`                           | Point at a different API host.                                                     |
| `--debug`                              | Print the request and response, for when a call fails and the error is not enough. |

## Use it in CI

Pass the key from your secret store and read `json` rather than the human format, which is
not a stable contract.

```bash theme={null}
courier send message \
  --api-key "$COURIER_API_KEY" \
  --message.to '{"user_id": "$USER_ID"}' \
  --message.template "deploy-complete" \
  --format json
```

The CLI exits non-zero on an API error, so a failed send fails the step without extra
handling. Use `--transform` to pull one field out for a later step rather than parsing the
whole body.
