Skip to main content
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, then set it in your shell:
export COURIER_API_KEY="your-api-key"
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 for all available flags.

Installation

npm install -g @trycourier/cli
This downloads a platform-specific binary via a postinstall step. No Node.js runtime is needed after installation.
Verify the installation:
courier --version

Command Structure

The CLI uses a resource-based pattern:
courier [resource] <command> [flags...]
Use --help on any command to see available flags:
courier send --help
courier messages list --help

Send Your First Notification

You don’t need a template to send your first notification. Pass the content inline and Courier handles the rest. Send an email directly
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 for how Courier evaluates routing order.
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 in the Courier dashboard, reference it by ID or alias.
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. 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
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.
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
courier profiles retrieve --user-id "user-123"

Check on a Notification

Every notification gets a message ID. Use it to check delivery status, inspect rendered content, or trace the full delivery timeline. Get the status of a sent notification
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.
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.
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.
courier messages list --format json \
  --transform "results.#(recipient==user-123)"
Inspect the notification Check the status. Look for DELIVERED, SENT, UNDELIVERABLE, or UNROUTABLE.
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?
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.
courier profiles retrieve --user-id "user-123" --format pretty

Send to Groups

Create a list and add subscribers
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
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.
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 to send to thousands of users in a single job.
# 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"

Work with Tenants

If you’re building a B2B product, tenants let you scope branding, preferences, and notification behavior per customer organization. Create a tenant
courier tenants create \
  --tenant-id "acme-corp" \
  --name "Acme Corp" \
  --properties '{"brandId": "brand-acme", "domain": "acme.com"}'
Add a user to a tenant
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.
courier send message \
  --message.to.user_id "user-123" \
  --message.to.tenant_id "acme-corp" \
  --message.template "welcome-email"

Trigger Automations

Automations let you orchestrate multi-step notification sequences (delays, conditions, batching) without writing that logic in your app. Invoke a saved automation
courier automations invoke \
  --template-id "onboarding-sequence" \
  --data '{"userId": "user-123", "plan": "pro"}'
List available automations
courier automations list

Manage Preferences

Let users control what they receive. Preferences 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
courier preferences retrieve --user-id "user-123" --format pretty
View preferences for a specific topic
courier preferences retrieve-topic \
  --user-id "user-123" \
  --topic-id "marketing-updates"

Output Formats

Every command supports structured output via the --format flag:
FormatDescription
autoDefault; human-readable for terminals, JSON for pipes
jsonJSON output
yamlYAML output
prettyColorized, indented JSON
rawRaw response body
jsonlNewline-delimited JSON (for streaming)
Filter output with GJSON syntax using --transform:
courier messages list --format json --transform "results.#.id"

Global Flags

FlagDescription
--api-keyOverride COURIER_API_KEY for this command
--base-urlUse a custom API URL
--formatOutput format (auto, json, yaml, pretty, raw, jsonl)
--transformFilter output with GJSON syntax
--debugShow HTTP request/response details
--version, -vPrint CLI version
--helpShow 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.
export COURIER_API_KEY="$COURIER_TEST_KEY"

courier send message \
  --message.to.user_id "smoke-test-user" \
  --message.template "welcome" \
  --format json
Store API keys as secrets in your CI provider (GitHub Actions secrets, GitLab CI variables, etc.) rather than hardcoding them.
Export recent notifications as JSON Useful for auditing or syncing with external systems.
courier messages list --format jsonl > messages-export.jsonl
Count notifications by status
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


Source code: trycourier/courier-cli