This page is written for AI coding agents. It gives you everything you need to send email and in-app inbox notifications with Courier without any prior context. Read it top to bottom before writing any code.
What Courier is
Courier is infrastructure that powers product-to-user communication. It provides a unified system for designing, sending, and managing notifications across email, SMS, push, chat, and in-app channels. Your app triggers an event, Courier determines the right channels and providers, renders the message, enforces user preferences, and delivers it in real time. One API, every channel.What you need before writing any code
- A Courier account — sign up here if the user hasn’t already
- An API key — find it at Settings > API Keys
- A provider connected (optional for first test) — Courier’s built-in email provider and Courier Inbox work with no setup in Test mode. Additional providers (SendGrid, Twilio, etc.) can be connected later at Integrations
Core concepts
Environments
Every workspace has two environments: Test and Production. They use different API keys. The key from Settings > API Keys defaults to Test. Sending to your own email from Test mode is fine for validation; do not send to end users from Test.Recipients
Theto field accepts five alternative shapes. Use whichever the user’s system already has:
{ "user_id": "user-123" }— looks up a stored profile for contact details (preferred){ "email": "alice@example.com" }— ad-hoc email, no profile needed{ "phone_number": "+14155550123" }— ad-hoc SMS (E.164 format required){ "list_id": "weekly-digest" }— sends to all list subscribers{ "audience_id": "churned-users" }— sends to a dynamic audience
Routing
Therouting object controls which channels Courier tries:
"all"— delivers to every channel in the list simultaneously"single"— tries channels left to right, stops at first success (fallback chain)
routing, Courier uses the notification template’s configured routing.
Inline content vs templates
Two ways to provide message content: Inline — content is in the API call itself. Good for dynamic or one-off messages:Profiles
A profile is a JSON object stored peruser_id that holds contact info and attributes:
user_id: "user-123" and that user has a stored email, Courier uses it automatically.
Idempotency
For transactional notifications (OTPs, order confirmations, billing alerts), always pass anIdempotency-Key header. If the same key is sent twice, Courier returns the stored response without re-sending. This includes error responses; replaying a key that failed does not retry with a corrected payload.
Step 1: Send your first message
This sends an email via the Courier Send API. In Test mode, Courier’s built-in email provider is available with no integration setup, but delivery depends on a valid recipient address and workspace email configuration (from address, brand).200 response with a requestId means Courier accepted the job; it does not mean the message was delivered. Use GET /messages/{requestId} or Message Logs to confirm per-provider delivery status.
Step 2: Store a user profile
Store contact information so you can send touser_id without repeating details in every call:
"to": { "user_id": "user-123" }. Courier looks up the stored profile and routes automatically. In SDKs, use client.profiles.create(userId, { profile: {...} }).
Step 3: Send with a template, data, and idempotency
For real transactional sends, use a template ID instead of inline content, pass dynamic data, and include an idempotency key. Replace"order-shipped" with the ID of a template you’ve created in the Courier dashboard. If the template doesn’t exist, the API returns {"status":"UNMAPPED"} with no error message.
-H "Idempotency-Key: order-shipped-ORD-456-user-123". In Node.js, pass { idempotencyKey: "..." } as the second argument to client.send.message(). In Python, use extra_headers={"Idempotency-Key": "..."}.
Step 4: Send to Inbox (in-app notifications)
Courier Inbox is an in-app notification feed. Messages sent to theinbox channel appear in a real-time UI component embedded in the user’s app. No external provider is needed; the Courier Inbox provider is built in.
Send a message to Inbox
Target theinbox channel in routing. Inbox requires user_id (not ad-hoc email), because the message is tied to a user’s feed:
Authenticate users for Inbox
Inbox SDKs use JWTs to authenticate. Your backend issues a token per user via the Courier auth endpoint:token field. Pass this JWT to the client-side SDK.
Embed the Inbox UI
Install a Courier Inbox SDK and render the component. The React example:Step 5: Debug a delivery
Check why a message was or wasn’t delivered:- No provider configured for the channel; add one at Integrations
- Missing contact info; the user profile has no email/phone for the channel being attempted
- Wrong environment; Test API key used with production user data, or vice versa
- Provider credentials invalid; check the integration config in the dashboard
What NOT to do
- Don’t use
PUT /profiles/{user_id}unless you intend to replace the entire profile. Any field not included in the PUT body is deleted. UsePOST /profiles/{user_id}(merge) for updates. - Don’t omit idempotency keys on transactional sends. Retries without idempotency keys cause duplicate notifications.
- Don’t use
method: "all"for transactional notifications (OTP, password reset, billing). Use"single"with fallback channels. - Don’t send to
user_idwithout a stored profile if the channel requires contact info. Courier won’t know where to deliver. - Don’t commit API keys to source code. Use environment variables or a secrets manager.
Key API endpoints and SDKs
Install:npm install @trycourier/courier (Node), pip install trycourier (Python), go get github.com/trycourier/courier-go/v4 (Go), npm install -g @trycourier/cli (CLI)
| Operation | Method | Path |
|---|---|---|
| Send a message | POST | /send |
| Get message status | GET | /messages/{message_id} |
| Get message history | GET | /messages/{message_id}/history |
| Create/merge a profile | POST | /profiles/{user_id} |
| Replace a profile | PUT | /profiles/{user_id} |
| Get a profile | GET | /profiles/{user_id} |
| Issue a JWT (for Inbox auth) | POST | /auth/issue-token |
| Get inbox messages | GET | /inbox/messages |
| Subscribe user to list | PUT | /lists/{list_id}/subscriptions/{user_id} |
| List notification templates | GET | /notifications |
| Cancel a pending message | POST | /messages/{message_id}/cancel |
For MCP server, CLI, and Skills setup, see Build with AI.
What’s Next
Inbox Overview
Full setup guide for in-app notifications across web and mobile.
MCP Server
Structured tool access for AI agents in Cursor, Claude Code, and Windsurf.
Courier CLI
All API endpoints from the command line.
API Reference
Full REST API documentation.