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

# Courier CLI

> Send messages, manage users, inspect delivery logs, and operate all Courier API endpoints from the command line.

The Courier CLI is a native binary that covers the full Courier REST API. It works on macOS, Linux, and Windows with no runtime dependencies.

## Prerequisite: Create an API Key

You need a Courier API key to authenticate CLI commands. [Create one in your Courier Settings](https://app.courier.com/settings/api-keys), then set it in your shell:

```bash theme={null}
export COURIER_API_KEY="your-api-key"
```

<Tip>
  If you pass `--api-key` on a command, it takes priority over the environment variable. This is useful for testing against a different workspace without changing your shell config. See [Global Flags](#global-flags) for all available flags.
</Tip>

## Installation

<Tabs>
  <Tab title="npm">
    ```bash theme={null}
    npm install -g @trycourier/cli
    ```

    This downloads a platform-specific binary via a postinstall step. No Node.js runtime is needed after installation.
  </Tab>

  <Tab title="Direct download">
    Download the latest binary for your platform from [GitHub Releases](https://github.com/trycourier/courier-cli/releases). Extract it and add it to your `PATH`.
  </Tab>
</Tabs>

Verify the installation:

```bash theme={null}
courier --version
```

## Command Structure

The CLI uses a resource-based pattern:

```bash theme={null}
courier [resource] <command> [flags...]
```

Use `--help` on any command to see available flags:

```bash theme={null}
courier send --help
courier messages list --help
```

## Send Your First Notification

You don't need a template to [send your first notification](/platform/sending/send-message). Pass the content inline and Courier handles the rest.

**Send an email directly**

```bash theme={null}
courier send message \
  --message.to.email "alex@example.com" \
  --message.content.title "New comment on your design file" \
  --message.content.body "Sara left a comment on Homepage Redesign: 'Love the new hero section — can we try a darker background?'"
```

**Send across multiple channels**

Route a notification to email first, then fall back to SMS if email can't be delivered. See [channel priority](/platform/sending/channel-priority) for how Courier evaluates routing order.

```bash theme={null}
courier send message \
  --message.to.email "alex@example.com" \
  --message.to.phone_number "+15551234567" \
  --message.content.title "New login detected" \
  --message.content.body "We noticed a new login to your account from {{city}}, {{country}}. If this wasn't you, reset your password immediately." \
  --message.data '{"city": "San Francisco", "country": "US"}' \
  --message.routing.method "single" \
  --message.routing.channels '["email", "sms"]'
```

**Send using a template**

Once you've built a [template](/platform/content/content-overview) in the Courier dashboard, reference it by ID or alias.

```bash theme={null}
courier send message \
  --message.to.user_id "user-123" \
  --message.template "usage-alert" \
  --message.data '{"resource": "API calls", "used": 8000, "limit": 10000, "percentage": 80, "upgradeUrl": "https://app.example.com/billing"}'
```

## Manage User Profiles

Courier needs to know where to reach your [users](/platform/users/users). Create profiles with their contact info so you can send by `user_id` instead of passing email and phone every time.

**Create a user profile**

```bash theme={null}
courier profiles create \
  --user-id "user-123" \
  --profile '{"email": "alex@example.com", "phone_number": "+15551234567", "name": "Alex Chen"}'
```

**Add custom attributes**

Custom data on the profile is available in all templates for that user.

```bash theme={null}
courier profiles create \
  --user-id "user-123" \
  --profile '{"custom": {"plan": "pro", "company": "Acme Corp", "locale": "en-US"}}'
```

This merges with the existing profile, so you won't overwrite the email or phone number you set earlier.

**Look up a user's profile**

```bash theme={null}
courier profiles retrieve --user-id "user-123"
```

## Check on a Notification

Every notification gets a [message ID](/platform/sending/sending-overview). Use it to check delivery status, inspect rendered content, or trace the full delivery timeline.

**Get the status of a sent notification**

```bash theme={null}
courier messages retrieve --message-id "1-abc123"
```

**View the full delivery timeline**

See every step a notification went through (queued, sent, delivered, opened, clicked) with timestamps.

```bash theme={null}
courier messages history --message-id "1-abc123"
```

**See what was actually rendered**

Check the final content Courier sent to the provider, after all template variables and routing logic were applied.

```bash theme={null}
courier messages output --message-id "1-abc123"
```

## Debug a Delivery Issue

When a user reports they didn't get a notification, run these in sequence to narrow it down.

**Find the notification**

Pull recent notifications for a specific user.

```bash theme={null}
courier messages list --format json \
  --transform "results.#(recipient==user-123)"
```

**Inspect the notification**

Check the status. Look for `DELIVERED`, `SENT`, `UNDELIVERABLE`, or `UNROUTABLE`.

```bash theme={null}
courier messages retrieve --message-id "1-abc123" --format pretty
```

**Trace the delivery steps**

The history shows exactly where things went wrong: did the provider reject it? Did routing skip a channel? Did a preference rule block it?

```bash theme={null}
courier messages history --message-id "1-abc123" --format pretty
```

**Verify the user's profile**

If the notification is `UNROUTABLE`, the user's profile is probably missing channel data.

```bash theme={null}
courier profiles retrieve --user-id "user-123" --format pretty
```

## Send to Groups

**Create a [list](/tutorials/sending/how-to-send-to-a-list-or-list-pattern-using-wildcarding) and add subscribers**

```bash theme={null}
courier lists update --list-id "beta-testers" --name "Beta Testers"

courier lists subscribe --list-id "beta-testers" --user-id "user-123"
courier lists subscribe --list-id "beta-testers" --user-id "user-456"
courier lists subscribe --list-id "beta-testers" --user-id "user-789"
```

**Send to the whole list**

```bash theme={null}
courier send message \
  --message.to.list_id "beta-testers" \
  --message.template "feature-announcement" \
  --message.data '{"feature": "Design Studio", "releaseDate": "2026-03-15"}'
```

**Send to multiple lists with a pattern**

If your list IDs follow a hierarchy (like `eng.frontend`, `eng.backend`), target all of them at once.

```bash theme={null}
courier send message \
  --message.to.list_pattern "eng.*" \
  --message.template "engineering-update"
```

## Send in Bulk

For large sends (product launches, monthly digests, migration emails), use the [bulk API](/api-reference/bulk/create-a-bulk-job) to send to thousands of users in a single job.

```bash theme={null}
# Create the job
courier bulk create --message.template "monthly-digest"

# Add recipients (up to 1,000 per call)
courier bulk add-users --job-id "job-abc" \
  --users '[{"user_id": "user-1", "data": {"highlights": 12}}, {"user_id": "user-2", "data": {"highlights": 7}}]'

# Start sending
courier bulk run --job-id "job-abc"

# Check progress
courier bulk retrieve --job-id "job-abc"
```

## Manage Templates

[Notification templates](/platform/content/content-overview) let you define reusable content and routing in Courier's designer, then send by template ID. The V2 workflow is: create → add content → create a routing strategy → publish → send.

**Create a draft template**

```bash theme={null}
courier notifications create \
  --notification '{"name": "password-reset", "tags": ["transactional"], "content": {"version": "2022-01-01", "elements": []}}' \
  --state "DRAFT"
```

The response includes the template ID (`nt_...`). Use that ID in all subsequent commands.

**Add content to the template**

```bash theme={null}
courier notifications put-content \
  --id "nt_01abc123" \
  --content '{"version": "2022-01-01", "elements": [{"type": "channel", "channel": "email", "elements": [{"type": "meta", "title": "Reset your password"}, {"type": "text", "content": "Click the link below to reset your password. This link expires in 15 minutes."}]}]}' \
  --state "DRAFT"
```

**Create a routing strategy**

A [routing strategy](/platform/content/template-designer/routing-configuration) controls which channels to use and in what order. You can reuse one strategy across multiple templates.

```bash theme={null}
courier routing-strategies create \
  --name "transactional-email" \
  --routing '{"method": "single", "channels": ["email"]}'
```

Copy the strategy ID (`rs_...`) from the response, then link it to your template:

```bash theme={null}
courier notifications replace \
  --id "nt_01abc123" \
  --notification '{"name": "password-reset", "tags": ["transactional"], "routing": {"strategy_id": "rs_01xyz"}, "content": {"version": "2022-01-01", "elements": []}}'
```

**Publish the template**

```bash theme={null}
courier notifications publish --id "nt_01abc123"
```

This returns 204 when successful. The template is now live and referenceable by alias (`password-reset`) or ID.

**Send using the template**

```bash theme={null}
courier send message \
  --message.to.user_id "user-123" \
  --message.template "password-reset" \
  --message.data '{"resetUrl": "https://app.example.com/reset?token=abc"}'
```

**List and inspect templates**

```bash theme={null}
# List all templates in your workspace
courier notifications list

# Check current published or draft content
courier notifications retrieve-content --id "nt_01abc123" --version draft
```

## Work with Tenants

If you're building a B2B product, [tenants](/platform/tenants/tenants-overview) let you scope branding, preferences, and notification behavior per customer organization.

**Create a tenant**

```bash theme={null}
courier tenants create \
  --tenant-id "acme-corp" \
  --name "Acme Corp" \
  --properties '{"brandId": "brand-acme", "domain": "acme.com"}'
```

**Add a user to a tenant**

```bash theme={null}
courier user-tenants add \
  --user-id "user-123" \
  --tenant-id "acme-corp"
```

**Send with tenant context**

This applies the tenant's brand and preferences automatically.

```bash theme={null}
courier send message \
  --message.to.user_id "user-123" \
  --message.to.tenant_id "acme-corp" \
  --message.template "welcome-email"
```

## Trigger Automations

[Automations](/platform/automations/automations-overview) let you orchestrate multi-step notification sequences (delays, conditions, batching) without writing that logic in your app.

**Invoke a saved automation**

```bash theme={null}
courier automations invoke \
  --template-id "onboarding-sequence" \
  --data '{"userId": "user-123", "plan": "pro"}'
```

**List available automations**

```bash theme={null}
courier automations list
```

## Manage Preferences

Let users control what they receive. [Preferences](/platform/preferences/preferences-overview) are enforced at send time; Courier won't deliver a notification if the user has opted out of that topic.

**Check a user's current preferences**

```bash theme={null}
courier preferences retrieve --user-id "user-123" --format pretty
```

**View preferences for a specific topic**

```bash theme={null}
courier preferences retrieve-topic \
  --user-id "user-123" \
  --topic-id "marketing-updates"
```

## Output Formats

Every command supports structured output via the `--format` flag:

| Format   | Description                                           |
| -------- | ----------------------------------------------------- |
| `auto`   | Default; human-readable for terminals, JSON for pipes |
| `json`   | JSON output                                           |
| `yaml`   | YAML output                                           |
| `pretty` | Colorized, indented JSON                              |
| `raw`    | Raw response body                                     |
| `jsonl`  | Newline-delimited JSON (for streaming)                |

Filter output with [GJSON syntax](https://github.com/tidwall/gjson/blob/master/SYNTAX.md) using `--transform`:

```bash theme={null}
courier messages list --format json --transform "results.#.id"
```

## Global Flags

| Flag              | Description                                                      |
| ----------------- | ---------------------------------------------------------------- |
| `--api-key`       | Override `COURIER_API_KEY` for this command                      |
| `--base-url`      | Use a custom API URL                                             |
| `--format`        | Output format (`auto`, `json`, `yaml`, `pretty`, `raw`, `jsonl`) |
| `--transform`     | Filter output with GJSON syntax                                  |
| `--debug`         | Show HTTP request/response details                               |
| `--version`, `-v` | Print CLI version                                                |
| `--help`          | Show command usage                                               |

## Scripting and CI/CD

**Smoke-test a notification after deploy**

Run this in your CI pipeline to verify notifications still work after a deploy.

```bash theme={null}
export COURIER_API_KEY="$COURIER_TEST_KEY"

courier send message \
  --message.to.user_id "smoke-test-user" \
  --message.template "welcome" \
  --format json
```

<Tip>
  Store API keys as secrets in your CI provider (GitHub Actions secrets, GitLab CI variables, etc.) rather than hardcoding them.
</Tip>

**Export recent notifications as JSON**

Useful for auditing or syncing with external systems.

```bash theme={null}
courier messages list --format jsonl > messages-export.jsonl
```

**Count notifications by status**

```bash theme={null}
courier messages list --format json \
  --transform "results.#.status" | sort | uniq -c
```

## AI Agent Usage

The CLI works as a zero-config tool for AI agents in Cursor, Claude Code, Codex, and similar environments. Install once, set `COURIER_API_KEY`, and agents can run Courier operations directly via shell commands. Every command supports `--format json` for machine-readable output.

## What's Next

<CardGroup cols={2}>
  <Card title="Manage Templates (CLI & MCP)" icon="rectangle-list" href="/tutorials/content/how-to-manage-templates-cli-mcp">
    Step-by-step tutorial: create, publish, send, and archive templates from the CLI
  </Card>

  <Card title="MCP Server" icon="microchip-ai" href="/tools/mcp">
    Structured AI tool access for sending messages, managing users, and more
  </Card>

  <Card title="API Reference" icon="code" href="/reference/get-started">
    Full REST API documentation
  </Card>
</CardGroup>

***

Source code: [trycourier/courier-cli](https://github.com/trycourier/courier-cli)
