"
}
}
}
}
}
}
```
Everything inside of `message.providers.vonage.override` will replace what Courier sends to the [Vonage SMS API](https://developer.vonage.com/en/messaging/sms/overview).
# What Is Courier?
Source: https://www.courier.com/docs/getting-started/introduction
Courier is a unified notification API: one call delivers across email, SMS, push, chat, and in-app with routing, retries, and preference enforcement.
## Why teams use Courier
Without a unified layer, teams manage separate SDKs and provider contracts per channel, build custom retry and failover logic, and have no shared preference or delivery-tracking system. Every new channel multiplies integration work.
Courier replaces that with a unified delivery pipeline and a shared set of controls.
### Built for developers
Integrate once and stay out of the way. Courier handles channel logic so your code doesn't have to.
**[Send API](/docs/platform/sending/sending-overview)** ✉️ — deliver across every channel from a single message model.
**[Multi-channel routing](/docs/platform/sending/choosing-your-sending-strategy)** 🔀 — define fallback chains or parallel fanout per message; Courier abstracts provider differences.
**[Reliability primitives](/docs/platform/sending/delivery-pipeline-resilience)** 🛡️ — automatic retries, provider failover, and idempotency keys prevent duplicates and dropped messages.
**[Agent-native tooling](/docs/tools/ai-onboarding)** 🤖 — CLI, MCP server, and machine-readable docs let coding agents discover and call every API from their workflow.
### Built for your whole team
After the initial setup, non-engineers can manage notification content and policy without touching code.
**[Template Designer](/docs/platform/content/content-overview)** 🎨 — a visual editor for building branded notifications with drag-and-drop blocks.
**[Hosted Preferences](/docs/platform/preferences/preferences-overview)** 🔔 — a drop-in preference center where users control their channels and topics; Courier enforces opt-outs at send time.
**[Tenant scoping](/docs/platform/tenants/tenants-overview)** 🏢 — isolate branding, preferences, and routing per customer account for B2B scenarios.
**[Journeys](/docs/platform/journeys/journeys-overview)** ⚡ — orchestrate multi-step sequences with triggers, delays, and conditions for digests, reminders, and onboarding flows.
## Get Started
Send your first notification with cURL, CLI, or SDKs.
Learn routing behavior, channel strategy, and delivery pipeline details.
Install Courier CLI, MCP, and skills for agent-assisted development.
A self-contained guide for AI coding agents with patterns, guardrails, and API reference.
# Quickstart
Source: https://www.courier.com/docs/getting-started/quickstart
Send your first Courier notification with one API call using cURL, the CLI, or a server SDK. No dashboard setup required, plus a path for AI agents.
## For Developers
Send a notification in under two minutes. All you need is an API key.
Sign up or log in to [Courier](https://app.courier.com), then copy your API key from [Settings > API Keys](https://app.courier.com/settings/api-keys).
One API call sends an email. Use cURL, the [CLI](/docs/tools/cli), or any of our [server SDKs](/docs/sdk-libraries/sdks-overview). Replace `YOUR_API_KEY` with your key and `you@example.com` with your email address.
```bash cURL theme={null}
curl -X POST https://api.courier.com/send \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": {
"to": { "email": "you@example.com" },
"content": {
"title": "Hello from Courier!",
"body": "You just sent your first notification. Nice work, {{name}}."
},
"data": { "name": "Developer" },
"routing": {
"method": "single",
"channels": ["email"]
}
}
}'
```
```bash CLI theme={null}
# npm install -g @trycourier/cli
export COURIER_API_KEY=YOUR_API_KEY
courier send message \
--message.to.email "you@example.com" \
--message.content.title "Hello from Courier!" \
--message.content.body "You just sent your first notification. Nice work, {{name}}." \
--message.data '{"name": "Developer"}' \
--message.routing.method "single" \
--message.routing.channels '["email"]'
```
```javascript Node.js theme={null}
// npm install @trycourier/courier
import Courier from "@trycourier/courier";
const client = new Courier({ apiKey: "YOUR_API_KEY" });
const response = await client.send.message({
message: {
to: { email: "you@example.com" },
content: {
title: "Hello from Courier!",
body: "You just sent your first notification. Nice work, {{name}}.",
},
data: { name: "Developer" },
routing: { method: "single", channels: ["email"] },
},
});
console.log("Sent! Request ID:", response.requestId);
```
```python Python theme={null}
# pip install trycourier
from courier import Courier
client = Courier(api_key="YOUR_API_KEY")
response = client.send.message(
message={
"to": {"email": "you@example.com"},
"content": {
"title": "Hello from Courier!",
"body": "You just sent your first notification. Nice work, {{name}}.",
},
"data": {"name": "Developer"},
"routing": {"method": "single", "channels": ["email"]},
}
)
print("Sent! Request ID:", response.request_id)
```
```ruby Ruby theme={null}
# gem install trycourier
require "trycourier"
client = Courier::Client.new "YOUR_API_KEY"
res = client.send({
message: {
to: { email: "you@example.com" },
content: {
title: "Hello from Courier!",
body: "You just sent your first notification. Nice work, {{name}}.",
},
data: { name: "Developer" },
routing: { method: "single", channels: ["email"] },
},
})
```
```go Go theme={null}
// go get github.com/trycourier/courier-go/v2
import (
"context"
"fmt"
"github.com/trycourier/courier-go/v2"
)
client := courier.CreateClient("YOUR_API_KEY", nil)
requestID, err := client.SendMessage(
context.Background(),
courier.SendMessageRequestBody{
Message: map[string]interface{}{
"to": map[string]string{"email": "you@example.com"},
"content": map[string]string{
"title": "Hello from Courier!",
"body": "You just sent your first notification. Nice work, {{name}}.",
},
"data": map[string]string{"name": "Developer"},
"routing": map[string]interface{}{
"method": "single",
"channels": []string{"email"},
},
},
},
)
fmt.Println("Sent! Request ID:", requestID)
```
The response includes a `requestId` you can use to track delivery:
```json theme={null}
{ "requestId": "1-67890abc-d1e2f3a4b5c6" }
```
Open [Message Logs](https://app.courier.com/logs) in your dashboard. You should see your message with a timeline showing each stage: accepted, routed, rendered, sent, and delivered.
If the message doesn't appear, double-check that your API key is correct and that you're viewing the right environment (Test vs Production).
That's it. One API call, one notification delivered.
***
## For AI Agents
If you're using an AI coding agent (Claude Code, Cursor, Codex, etc.), set your API key and install your tooling, then send your first message with the CLI or cURL.
```bash theme={null}
export COURIER_API_KEY="your-api-key"
```
```bash theme={null}
npm install -g @trycourier/cli
```
Add this to your `mcp.json`:
```json theme={null}
{
"mcpServers": {
"courier": {
"url": "https://mcp.courier.com",
"headers": {
"api_key": "XXXX"
}
}
}
}
```
```bash theme={null}
claude mcp add --transport http courier https://mcp.courier.com --header api_key:XXXX
```
```bash CLI theme={null}
courier send message \
--message.to.email "you@example.com" \
--message.content.title "Hello" \
--message.content.body "Sent from an agent"
```
```bash cURL theme={null}
curl -X POST https://api.courier.com/send \
-H "Authorization: Bearer $COURIER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": {
"to": { "email": "you@example.com" },
"content": { "title": "Hello", "body": "Sent from an agent" },
"routing": { "method": "single", "channels": ["email"] }
}
}'
```
Use the [Agent Quickstart](/docs/tools/agent-quickstart) for profile storage, idempotency, inbox delivery, JWT auth, and common anti-patterns.
Courier docs are available as machine-readable indexes for AI agents:
[llms.txt](https://www.courier.com/docs/llms.txt) and
[llms-full.txt](https://www.courier.com/docs/llms-full.txt).
### Copy for Cursor / Claude
Paste one of these blocks directly into your coding agent. Each block includes setup, key API patterns, error handling, and guardrails.
```javascript Node.js theme={null}
/**
* Courier agent quick reference (Node.js)
*
* Setup:
* npm install @trycourier/courier
* export COURIER_API_KEY="your-api-key"
*
* Core endpoints:
* POST /send
* POST /profiles/{user_id} (merge profile)
* PUT /profiles/{user_id} (replace entire profile)
* POST /auth/issue-token
*
* Error/status patterns:
* 401 Unauthorized -> invalid/missing API key
* UNDELIVERABLE -> invalid recipient or blocked domain
* UNROUTABLE -> no provider or missing profile channel data
* UNMAPPED -> missing template id
*
* Guardrails:
* - Use POST /profiles/{user_id} for updates; PUT replaces and can delete fields.
* - Use method:"single" for transactional sends (OTP/billing), method:"all" for fanout.
* - Pass Idempotency-Key via headers; do not rely on idempotencyKey option.
*/
import Courier from "@trycourier/courier";
const client = new Courier(); // Reads COURIER_API_KEY
// 1) Merge a user profile
await client.profiles.create("user-123", {
profile: {
email: "alice@example.com",
name: "Alice",
phone_number: "+14155550123",
},
});
// 2) Send transactional email with idempotency
const transactional = await client.send.message(
{
message: {
to: { user_id: "user-123" },
content: {
title: "Your order shipped",
body: "Order #{{orderId}} is on its way.",
},
data: { orderId: "ORD-456" },
routing: { method: "single", channels: ["email", "sms"] },
},
},
{ headers: { "Idempotency-Key": "order-ORD-456-user-123" } }
);
// 3) Send informational fanout to email + inbox
await client.send.message({
message: {
to: { user_id: "user-123" },
content: {
title: "New activity",
body: "Your weekly summary is ready.",
},
routing: { method: "all", channels: ["email", "inbox"] },
},
});
// 4) Issue JWT for Inbox client auth
const { token } = await client.auth.issueToken({
scope: "user_id:user-123 inbox:read:messages inbox:write:events",
expires_in: "1 day",
});
console.log({ requestId: transactional.requestId, inboxJwt: token });
```
```python Python theme={null}
"""
Courier agent quick reference (Python)
Setup:
pip install trycourier
export COURIER_API_KEY="your-api-key"
Core endpoints:
POST /send
POST /profiles/{user_id} (merge profile)
PUT /profiles/{user_id} (replace entire profile)
POST /auth/issue-token
Error/status patterns:
401 Unauthorized -> invalid/missing API key
UNDELIVERABLE -> invalid recipient or blocked domain
UNROUTABLE -> no provider or missing profile channel data
UNMAPPED -> missing template id
Guardrails:
- Use POST /profiles/{user_id} for updates; PUT replaces and can delete fields.
- Use method:"single" for transactional sends (OTP/billing), method:"all" for fanout.
- Include Idempotency-Key on transactional sends to avoid duplicates on retries.
"""
from courier import Courier
client = Courier() # Reads COURIER_API_KEY
# 1) Merge a user profile
client.profiles.create(
"user-123",
profile={
"email": "alice@example.com",
"name": "Alice",
"phone_number": "+14155550123",
},
)
# 2) Send transactional email with idempotency
transactional = client.send.message(
message={
"to": {"user_id": "user-123"},
"content": {
"title": "Your order shipped",
"body": "Order #{{orderId}} is on its way.",
},
"data": {"orderId": "ORD-456"},
"routing": {"method": "single", "channels": ["email", "sms"]},
},
extra_headers={"Idempotency-Key": "order-ORD-456-user-123"},
)
# 3) Send informational fanout to email + inbox
client.send.message(
message={
"to": {"user_id": "user-123"},
"content": {
"title": "New activity",
"body": "Your weekly summary is ready.",
},
"routing": {"method": "all", "channels": ["email", "inbox"]},
}
)
# 4) Issue JWT for Inbox client auth
auth_response = client.auth.issue_token(
scope="user_id:user-123 inbox:read:messages inbox:write:events",
expires_in="1 day",
)
print({"request_id": transactional.request_id, "inbox_jwt": auth_response.token})
```
***
## FAQ
Not necessarily. Courier includes a built-in email provider, so email works out of the box in Test mode. For production email, SMS, push, or chat you'll need to connect a provider in [Integrations](https://app.courier.com/integrations). The [Inbox](/docs/platform/inbox/inbox-overview) and Toast channels also work without any external provider.
Yes. Courier's [Design Studio](/docs/platform/content/design-studio/design-studio-overview) lets you build notifications visually with drag-and-drop blocks, reusable routing configurations, and version history. Reference templates by ID in your send call. You can also [manage templates programmatically](/docs/platform/content/design-studio/manage-templates-api) via the Templates API.
Add a provider for the channel you want in [Integrations](https://app.courier.com/integrations), then update the `routing` object in your send call. To send to multiple channels, set `method` to `"all"` and list the channels you want. See [How Sending Works](/docs/platform/sending/sending-overview) for details on routing and fallback behavior.
We have official SDKs for Node.js, Python, Ruby, Go, Java, PHP, and C#, plus mobile SDKs for iOS, Android, React Native, and Flutter. See the full list on the [SDKs overview](/docs/sdk-libraries/sdks-overview). You can also call the [REST API](/docs/api-reference/send/send-a-message) directly from any language.
## What's Next
Understand routing, channels, and the delivery pipeline
Build a reusable notification in Design Studio
Embed a real-time notification feed in your app; no provider needed
Connect your AI coding agent to Courier via MCP or CLI
# Glossary
Source: https://www.courier.com/docs/help/glossary
Look up Courier terms from API keys and audiences to webhooks and workspaces, each defined in plain language and linked to its reference page.
## A
**[API Key](/docs/platform/workspaces/environments-api-keys)** - A secret token used to authenticate requests to Courier's REST API. Each environment has its own API keys, and keys can be scoped to specific permissions.
**[Audience](/docs/platform/users/audiences)** - A dynamic user collection that automatically updates based on filter rules. Unlike static lists, audiences automatically include or exclude users as their profile data changes.
**[Automation](/docs/platform/automations/automations-overview)** - A multi-step workflow that orchestrates complex messaging sequences with conditional logic, delays, digests, and multiple notification steps.
## B
**[Batching](/docs/platform/automations/batching)** - Grouping multiple automation triggers into a single consolidated action within an automation step.
**[Brand](/docs/platform/content/brands/brands-overview)** - A visual template that applies consistent styling to email notifications, including logos, colors, and layout.
**[Bulk Send](/docs/tutorials/sending/how-to-send-bulk-notifications)** - Sending the same notification to many recipients in a single API call using a job-based workflow.
## C
**[Channel](/docs/external-integrations/integrations-overview)** - A communication method like email, SMS, push notifications, in-app inbox, or direct messaging (Slack, Discord, etc.).
**[Channel Priority](/docs/platform/sending/channel-priority)** - The order in which different communication channels are attempted when delivering a notification.
**[Client Key](/docs/platform/inbox/authentication)** - A publishable key used to authenticate client-side SDKs (Inbox, Toast, React components). Unlike API keys, client keys are safe to expose in frontend code.
**[Conditions](/docs/platform/content/template-settings/send-conditions)** - Logic that enables or disables content blocks, channels, or entire notifications based on user data or other criteria.
**[Content Block](/docs/platform/content/template-designer/template-designer-overview)** - A reusable, responsive content component (text, image, action button, etc.) used inside notification templates in the Template Designer.
**[Courier Create](/docs/platform/create/create-overview)** - An embeddable notification designer that lets your users create and edit notification templates within your application.
## D
**[Delay](/docs/platform/sending/delay)** - A pause step in automations that waits a specified duration or until a specific time before continuing.
**Delivery** - The final step where a message reaches the recipient through a provider.
**[Designer](/docs/platform/content/template-designer/template-designer-overview)** - Courier's visual editor for creating and editing notification templates.
**Device Token** - A unique identifier for app-device combinations issued by Apple (APNs) or Google (FCM) push notification gateways. Stored in [user profiles](/docs/platform/users/users).
**[Digest](/docs/platform/automations/digest)** - A feature that batches multiple notifications into a single consolidated message, reducing notification fatigue.
**[Draft / Published](/docs/platform/content/template-settings/general-settings)** - The two states of a notification template. Draft changes are only visible in the test environment until you publish them to production.
## E
**[Elemental](/docs/platform/content/elemental/elemental-overview)** - A JSON-based syntax for describing notification content programmatically that works across all channels.
**[Environment](/docs/platform/workspaces/environments-api-keys)** - Separate instances (test and production) within a workspace that allow safe development without affecting live notifications.
**Event** - A trigger identifier that initiates notifications, typically the notification template ID passed in the `template` field of a send request.
## F
**[Failover](/docs/platform/sending/failover)** - Automatic switching to backup providers when a primary provider fails to deliver.
**[Feed](/docs/platform/inbox/inbox-overview)** - A filtered stream of messages in the Inbox component. Feeds let you organize messages into categories like "Notifications" and "Comments" using tabs.
## G
**[Guardrails](/docs/platform/sending/send-limits)** - Safety features like send limits that prevent accidental sends to large audiences or excessive notifications to individual users.
## H
**[Handlebars](/docs/platform/content/template-designer/handlebars-designer)** - A templating language used in the Template Designer for dynamic content insertion, conditionals, and loops. See also [Handlebars Helpers](/docs/platform/content/template-designer/handlebars-helpers).
## I
**[Idempotency Key](/docs/reference/get-started#idempotency)** - A unique identifier sent with API requests to prevent duplicate processing. If the same key is sent twice, Courier returns the original response.
**[Inbox](/docs/platform/inbox/inbox-overview)** - Courier's built-in in-app notification center component for web and mobile applications.
**Inline Content** - Notification content defined directly in the [Send API](/docs/platform/sending/send-message) request body rather than referencing a stored template.
**[Integration](/docs/external-integrations/integrations-overview)** - A connection between Courier and a notification provider (SendGrid, Twilio, etc.) that handles actual message delivery.
## J
**[Jsonnet](/docs/platform/content/content-blocks/jsonnet-blocks)** - A data templating language used in content blocks and the [webhook designer](/docs/platform/content/template-designer/jsonnet-webhook-designer) for advanced data transformation.
**[JWT (JSON Web Token)](/docs/platform/inbox/authentication)** - A secure token used for client-side authentication, required for Inbox and other frontend SDK components.
## L
**[List](/docs/platform/users/audiences)** - A static group of users that must be manually managed, unlike audiences which update automatically.
**[Locale](/docs/platform/content/localization)** - A language or regional variant (e.g., `en-US`, `fr-FR`) used for localizing notification content.
**[Logs](/docs/platform/analytics/message-logs)** - A timeline of sent messages with delivery status, provider responses, and rendered content.
## M
**[MCP (Model Context Protocol)](/docs/tools/mcp)** - A protocol that lets AI agents interact with Courier programmatically.
**Message** - A single instance of a notification sent to a specific recipient.
**[Message Status](/docs/platform/analytics/message-logs)** - The delivery state of a message as it moves through the pipeline: enqueued, sent, delivered, opened, clicked, or undeliverable.
## N
**Notification** - A message template that can be sent repeatedly through one or more channels, containing [variables](/docs/platform/content/variables/inserting-variables) for personalization.
## O
**Override** - A way to modify the request body or configuration sent to a provider, useful for passing provider-specific options not exposed in the template designer.
**[Outbound Webhook](/docs/platform/workspaces/outbound-webhooks)** - A webhook that Courier fires when specific events occur (message sent, delivered, etc.), allowing you to sync delivery data to external systems.
## P
**[Preferences](/docs/platform/preferences/preferences-overview)** - User-controlled settings that determine which notifications they receive and through which channels.
**[Profile](/docs/platform/users/users)** - A JSON object storing a user's contact information, device tokens, and custom attributes.
**[Provider](/docs/external-integrations/integrations-overview)** - The downstream service (Twilio, SendGrid, etc.) that delivers notifications to recipients.
## R
**Recipient** - The end user who receives notifications, identified by their [profile](/docs/platform/users/users).
**[Routing](/docs/platform/sending/choosing-your-sending-strategy)** - The logic that determines which channels and providers are used to deliver notifications.
## S
**[Send Conditions](/docs/platform/content/template-settings/send-conditions)** - Rules evaluated at send time that determine whether a notification should be delivered based on data or logic.
**[Send Limit](/docs/platform/sending/send-limits)** - A cap on how many notifications can be sent to a user, topic, or tenant within a time period. Different from API rate limits.
**[Subscription Topic](/docs/platform/preferences/preferences-overview)** - A category of notifications (e.g., "Marketing", "Order Updates") that users can opt in or out of through preferences. Configured in the [Preferences Editor](/docs/platform/preferences/preferences-editor).
## T
**[Template](/docs/platform/content/template-designer/template-designer-overview)** - A reusable message design created in the Template Designer that defines content structure and can be sent to multiple recipients.
**[Template Approval](/docs/platform/content/template-approval-workflow)** - A workflow requiring review before templates can be published to production.
**[Tenant](/docs/platform/tenants/tenants-overview)** - An organization or customer group in multi-tenant applications, allowing you to scope branding, preferences, and data to specific organizations.
**[Throttle](/docs/platform/automations/throttle)** - An automation step that limits how frequently a notification can be triggered for a given scope (per-user, global, or dynamic key).
**[Toast](/docs/platform/inbox/notify-with-toasts)** - A transient pop-up notification shown by the Inbox SDK components when a new message arrives.
**Trigger** - An action or event that starts an [automation](/docs/platform/automations/automations-overview) or sends a notification.
## V
**[Variable](/docs/platform/content/variables/inserting-variables)** - A placeholder in notification templates (like `{name}` or `{order_id}`) that gets replaced with actual data when sending.
## W
**[Workspace](/docs/platform/workspaces/workspaces-overview)** - The top-level organizational unit containing all your Courier resources, environments, team members, and settings.
# Courier Help Center
Source: https://www.courier.com/docs/help/index
Find Courier's support contact, platform status page, changelog, and security and compliance portal in one place when you need help.
If you're having issues with the Courier platform, you can reach out to our support team and check other resources below.
You can contact our support team here.
**[support@courier.com](mailto:support@courier.com)**
Monitor and subscribe to alerts regarding Courier infrastructure health.
**See Status**
Stay up to date with new releases and bug fixes to the Courier platform.
**View the Changelog**
Review Courier's security posture, compliance certifications, and data handling practices.
**Security Portal**
# Template Analytics
Source: https://www.courier.com/docs/platform/analytics/analytics
Track send volume, delivery rates, opens, clicks, and errors per template. Filter by channel and date range across 7-, 30-, or 90-day windows.
We currently support template-level analytics that lets you gain insights on Send volume and Channel performances.
## Analytics Overview
The [Analytics tab](https://app.courier.com/analytics) provides an overview of metrics for individual templates, enabling you to track how a notification template performs across different channels and over selected time periods. You can adjust the analytics time window (last 7, 30, or 90 days) to better analyze trends over different periods.
The main analytics page provides two key visualizations:
* **Send volume chart** - Visualizes how notification send volume has evolved over time
* **Template performance table** - Shows total sends and error rates for each template in the selected time period
### Template Metrics
You can drill into individual templates to view channel-specific performance metrics, including delivery, open, and click rates where available. This lets you compare how different channels perform for a particular notification type.
### Delivered Events
Some email providers like [SendGrid](/docs/external-integrations/email/sendgrid) offer granularity for their API keys, and will need [further customization](https://sendgrid.com/en-us/blog/introducing-api-key-permissions) so that `delivered` events can be sent to Courier.
For Courier to receive `delivered` events from SendGrid, your [API key permissions](https://sendgrid.com/en-us/blog/introducing-api-key-permissions) must include `email activity` and `inbound parse`.
# Analytics Overview
Source: https://www.courier.com/docs/platform/analytics/analytics-overview
Track notification delivery, monitor status across channels, and debug issues with message logs, template analytics, and audit trails.
Courier tracks every notification from API request through routing, rendering, provider handoff, and delivery. Use message logs to debug individual sends, template analytics to compare channel performance, and audit trails to review workspace changes.
## Key Concepts
### Message Statuses
Every notification moves through a lifecycle: **Enqueued** (received by Courier), **Routed** (channel selected), **Sent** (handed to provider), **Delivered** (provider confirmed delivery), **Opened/Clicked** (user engaged). If something goes wrong, you'll see **Undeliverable** or **Unroutable** with a reason.
### Environment Isolation
Logs and analytics are scoped to the environment (Production or Test) of the API key used to send. Sends made with a test key only appear in the Test dashboard, and vice versa.
### Retention
Message logs are retained based on your plan tier. Check your plan details or contact support for specific retention windows.
## Common Workflows
**Debug a failed send**: Open [Message Logs](/docs/platform/analytics/message-logs) and filter by recipient, status, or time range. Each log entry shows a timeline of events (enqueued, routed, sent, delivered) plus the rendered content that was sent to the provider. If a send failed, the log tells you exactly where and why.
**Compare channel performance**: Open [Template Analytics](/docs/platform/analytics/analytics) to see send volume, delivery rates, opens, and clicks broken down by channel and provider. Use this to decide whether to prioritize email over push for a given template, or to spot a provider with an unusually high error rate.
**Audit workspace changes**: The [Audit Trail](/docs/platform/analytics/audit-trail) records who changed what and when; API key rotations, user invitations, integration updates, and template publications. Use it for compliance reporting or to trace unexpected configuration changes.
**Track engagement with custom domains**: If you use a custom domain for email tracking links, configure it in [Custom Domain Tracking](/docs/platform/analytics/custom-domain-tracking) so open and click events resolve to your brand instead of Courier's default domain.
## Next Steps
Debug individual sends with delivery timelines
Compare channel and provider performance
Track workspace configuration changes
Brand your email tracking links
# Audit Trail
Source: https://www.courier.com/docs/platform/analytics/audit-trail
Track workspace user activity including API key changes, user actions, publishing events, and integration updates.
**Availability**: Audit Trail is available for Enterprise customers. Contact [Courier Support](mailto:support@courier.com) for access.
The Audit Trail provides a record of security-relevant events in your workspace. Use it to monitor API key management, user access changes, publishing activity, and integration configuration for compliance and security purposes.
## Supported Events
### API Keys
| Event | Description |
| ----------------- | ----------------------------- |
| `API Key Created` | New API key generated |
| `API Key Deleted` | API key removed |
| `API Key Rotated` | API key credentials refreshed |
### Users
| Event | Description |
| ------------------- | ----------------------------- |
| `User Invited` | New user invited to workspace |
| `User Role Changed` | User's permissions updated |
| `User Deleted` | User removed from workspace |
| `User Logout` | User signed out |
### Workspace Settings
| Event | Description |
| -------------------------------------------- | -------------------------------------------- |
| `Workspace Name Changed` | Workspace renamed |
| `Workspace Discoverability Enabled/Disabled` | Controls whether workspace appears in search |
| `Workspace Accessibility Changed` | Workspace access settings updated |
| `SSO Required Enabled/Disabled` | Single sign-on requirement toggled |
| `Click-Through Tracking Enabled/Disabled` | Link tracking toggled |
| `Email Open Tracking Enabled/Disabled` | Open tracking pixel toggled |
| `Guard Rails Updated` | Workspace safety limits changed |
### Templates
| Event | Description |
| ----------------------------------------- | ------------------------------------- |
| `Notification Published` | Template published to production |
| `Notification Deleted` | Template removed |
| `Notification Duplicated` | Template copied |
| `Notification Draft Created` | New draft version created |
| `Notification Rolled Back` | Template reverted to previous version |
| `Notification Subscription Topic Changed` | Template's preference topic updated |
### Brands
| Event | Description |
| ----------------------- | ----------------------- |
| `Brand Created` | New brand added |
| `Brand Updated` | Brand settings modified |
| `Brand Published` | Brand changes published |
| `Brand Deleted` | Brand removed |
| `Default Brand Updated` | Default brand changed |
### Automations
| Event | Description |
| ------------------------------- | -------------------- |
| `Automation Template Published` | Automation published |
| `Automation Template Deleted` | Automation removed |
### Preferences
| Event | Description |
| ------------------------------------------ | --------------------------------------- |
| `Preferences Page Published` | Hosted preferences page published |
| `Preferences Notification Linked` | Template linked to preference topic |
| `Preferences Notification Unlinked` | Template unlinked from preference topic |
| `Preferences Section Created` | New preference section added |
| `Preferences Section Deleted` | Preference section removed |
| `Preferences Section Channel Added` | Channel added to section |
| `Preferences Section Channel Removed` | Channel removed from section |
| `Preferences Topic Created` | New preference topic added |
| `Preferences Topic Deleted` | Preference topic removed |
| `Preferences Topic Default Status Changed` | Topic opt-in/out default updated |
### Outbound Integrations
| Event | Description |
| ----------------------------------------------------------------------- | ------------------------------- |
| `Segment Source Created/Archived/Updated/Restored` | Segment integration changes |
| `Rudderstack Source Created/Archived/Enabled/Disabled/Updated/Restored` | Rudderstack integration changes |
| `Datadog Source Created/Archived/Enabled/Disabled/Updated/Restored` | Datadog integration changes |
| `NewRelic Source Created/Archived/Enabled/Disabled/Updated/Restored` | NewRelic integration changes |
## API Access
Audit trail events are accessible via the [Audit Events API](/docs/api-reference/audit-events/get-all-audit-events). Use the API to programmatically retrieve events for external logging, SIEM integration, or custom dashboards.
# Custom Domain Tracking
Source: https://www.courier.com/docs/platform/analytics/custom-domain-tracking
Replace the default ct0.app link-tracking domain with a branded subdomain for Enterprise workspaces, covering DNS setup and verification.
**Availability**: Custom Domain Tracking is available for Enterprise customers. Contact [Courier Support](mailto:support@courier.com) or your account team for access.
By default, Courier uses `ct0.app` for link tracking. Enterprise customers can use a custom tracking domain (e.g., `tracking.mywebsite.com`) for a more branded experience and improved deliverability.
## Why Use Custom Domain Tracking?
* **Brand consistency**: Links in emails display your domain instead of a third-party tracker
* **Improved trust**: Recipients are more likely to click links from a recognizable domain
* **Better deliverability**: Custom domains can help avoid spam filters that flag common tracking domains
## Setup Process
Custom domain tracking setup requires coordination with Courier Support.
Contact Courier Support with your desired tracking subdomain, typically something like `tracking.mywebsite.com`.
Courier will provide two DNS records to add:
1. **Certificate validation record**: Required for Courier's infrastructure to accept requests from your domain. Courier Support will provide the specific values.
2. **CNAME record**: Point your tracking subdomain to Courier's tracking infrastructure.
```
tracking.mywebsite.com CNAME tracking.ct0.app
```
After DNS records propagate, Courier Support will enable the feature for your workspace. Once activated, all tracked links in your notifications will use your custom domain.
## Verification
After setup, test by sending a notification with tracked links. The links should resolve through your custom domain rather than `ct0.app`.
# Message Logs
Source: https://www.courier.com/docs/platform/analytics/message-logs
Inspect the full delivery lifecycle of every Courier message. View statuses from ENQUEUED to DELIVERED and CLICKED, filtered by provider or error.
The Courier Message Logs provide a timeline and insights into the status of your notifications, recipients, lists, and automations. Each step in the send status has a visual representation.
The message logs display key information in the summary view:
* The **Status** of each message send request
* The **Notification** and **Recipient** associated with each send
* The **Provider** channels for the notification
## Message Statuses
Every message in Courier progresses through a series of statuses. The diagram below shows the delivery and engagement lifecycle for a typical message.
### Status Lifecycle
```mermaid actions={false} theme={null}
flowchart TD
A[ENQUEUED] --> B[ROUTED]
B --> C[SENT]
C --> D[DELIVERED]
C --> E[OPENED]
C --> F[CLICKED]
D --> E
E --> F
A -.-> U[UNROUTABLE]
C -.-> X[UNDELIVERABLE]
```
Statuses are tracked by two independent systems:
* **Provider confirmation**: `DELIVERED` depends on your email provider asynchronously confirming that the recipient's mail server accepted the message. This can be delayed by minutes or hours depending on the provider, and may never arrive at all.
* **Courier tracking**: `OPENED` and `CLICKED` are tracked by Courier directly via a tracking pixel and link redirect. They fire as soon as the recipient acts, independent of provider delivery confirmation.
Because these systems operate independently, you may receive `OPENED` or `CLICKED` before `DELIVERED`, or without `DELIVERED` arriving at all. If you're monitoring delivery, treat `OPENED` and `CLICKED` as confirmation that the message was received.
### Delivery Statuses
| Status | Description |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Queued** | Courier has received a valid Send API request and is processing the notification. |
| **Routed** | Channel routing is complete; the message is ready for delivery. |
| **Sent** | Courier sent the request to the downstream provider. A `200` response from Courier does not guarantee delivery; the provider may still reject the message. |
| **Delivered** | The provider confirmed the message was accepted. For email, this means it reached the inbox. For SMS, not all providers return delivery confirmation. |
| **Opened** | The recipient opened the notification (email with open tracking enabled, or inbox/push via SDK events). |
| **Clicked** | The recipient clicked a link in the notification (requires link tracking enabled). |
### Processing Statuses
| Status | Description |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| **Delayed** | The message is scheduled for future delivery. |
| **Digested** | The message was added to a digest queue and will be sent as part of a batched notification. |
| **Filtered** | The message was filtered out by routing rules, preferences, or send conditions. |
| **Throttled** | The message is being rate-limited due to [send limits](/docs/platform/sending/send-limits) or workspace quota. |
| **Simulated** | The message was sent with a [mock key](/docs/platform/workspaces/environments-api-keys), simulating the lifecycle without delivery to the provider. |
### Error Statuses
| Status | Description |
| ----------------- | ---------------------------------------------------------------------------------------------- |
| **Undeliverable** | The provider rejected the message or delivery failed. |
| **Unmapped** | The Notification ID does not exist or the Event ID is not mapped to a notification. |
| **Unroutable** | No valid delivery route was found (e.g., missing recipient address or no configured channels). |
| **Canceled** | The message delivery was canceled before being sent. |
**Has Errors** is not a status but a filter indicator showing that the message timeline includes an error response.
### Terminal vs. Intermediate Statuses
Courier's status model is **monotonic** — a message's status only moves forward through the lifecycle. Understanding which statuses are terminal (final) and which are intermediate (in-progress) helps you build accurate delivery tracking:
| Category | Statuses | Meaning |
| ------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Intermediate** | `ENQUEUED`, `ROUTED`, `SENT` | The message is still being processed or awaiting provider confirmation. These are not final outcomes — a message at `SENT` may still progress to `DELIVERED`, `OPENED`, or `UNDELIVERABLE`. |
| **Terminal (delivery)** | `DELIVERED`, `UNDELIVERABLE` | The delivery attempt has a final outcome. `DELIVERED` means the provider confirmed acceptance; `UNDELIVERABLE` means the provider rejected the message or delivery failed. |
| **Terminal (engagement)** | `OPENED`, `CLICKED` | The recipient interacted with the message. These can follow `DELIVERED` or even `UNDELIVERABLE` in edge cases (e.g., a security scanner loading the tracking pixel on a bounced email). |
| **Terminal (routing)** | `UNROUTABLE`, `CANCELED`, `FILTERED`, `UNMAPPED` | The message never reached a provider. |
`SENT` is not a terminal status. A message at `SENT` means Courier handed it to the provider, but the provider has not yet confirmed delivery. If a message stays at `SENT` indefinitely, it typically means delivery tracking is not configured (e.g., missing provider webhooks or polling). Check your provider's webhook or delivery status configuration.
### Channel-Specific Behavior
| Event | Email | Push (APNS/FCM) | Inbox | SMS | Chat |
| ------------- | -------------------------------- | --------------------------------- | --------------------------------- | ---------------------------------------- | -------------------------- |
| **Delivered** | Provider confirms inbox delivery | Delivery Rate = 100% - Error Rate | Delivery Rate = 100% - Error Rate | Not all providers return delivery status | Provider confirms delivery |
| **Opened** | Requires open tracking | Not supported | SDK push events | n/a | n/a |
| **Clicked** | Requires link tracking | SDK click events | SDK click events | n/a | Requires link tracking |
### Inbox and Channel Sync
Notification event states are synchronized between the [inbox](/docs/platform/inbox/inbox-overview) and other channels within a notification template. For example, if a notification was sent with both `inbox` and `email` channels, opening the email will mark the inbox notification as `read`.
This sync only works one way. Reading an inbox message will not mark an email as `opened`.
## Viewing Logs
### Histogram
The logs histogram breaks down delivery metrics for specific days, grouping events in color-coordinated graphs.
You can drag-select within the histogram to filter to a custom date range.
### Log Detail View
Click any notification in the list view to open the log details, which includes:
* **Summary** - Message and Recipient IDs, timestamps for each stage (Enqueued, Sent, First Delivery, etc.)
* **Timeline** - Detailed timeline of every step; click any event to view additional details
To investigate errors, click the `Error Encountered` event in the timeline to review the error message.
## Filtering Logs
Filter your message logs to find specific notifications:
* **Date** - Filter by date range (limited by your account's [log retention](#log-retention) period)
* **Recipient** - Filter by email address or Recipient ID
* **Notification** - Filter by specific notification template
* **Status** - Filter by one or more statuses: Queued, Sent, Delivered, Opened, Clicked, Undeliverable, Unmapped
* **Errors** - Show only notifications with errors in their timeline
* **Provider** - Filter by one or more integrated providers
Unmapped means the Event ID does not [map to a notification](https://help.courier.com/en/articles/4189555-send-basics-how-notifications-are-triggered-and-sent-with-courier) or the Notification ID is invalid.
## Rendered Content
The Courier [Messages API](/docs/api-reference/sent-messages/get-message-content) lets you retrieve the rendered output of any notification in your send history. This is useful for debugging and verifying what was actually sent to providers.
To fetch the rendered content for a message:
1. **Fetch message history** - Use GET `/messages/:message_id/history` with the message ID from your send response. You can use the history [endpoint](/docs/api-reference/sent-messages/get-message-history) or the `getMessageHistory` SDK function.
2. **Fetch message content** - Use the root-relative URL from the `RENDERED` event in the history response, appended to the Courier API host: `https://api.courier.com`
### Rendered Content for Emails
HTML content is returned with `content-type: text/html` as raw HTML:
```plaintext theme={null}
content-type: text/html
content-length: 32015
date: Tue, 26 Oct 2021 17:10:10 GMT
x-amzn-requestid: e9d54717-3cb0-4f1e-a47a-b64317738596
access-control-allow-origin: *
strict-transport-security: max-age=31536000;includeSubDomains;preload
```
## Log Retention
Courier retains message logs for different periods depending on your account type:
* **Free and Pro accounts**: Logs are retained for **30 days**
* **Contracted accounts**: Logs are retained for **1 year**
The Log date filter in the Message Logs interface will only allow you to filter and view logs within your account's retention period. Logs older than the retention period are automatically deleted and cannot be recovered.
**Enterprise Customers**: If you need longer log retention periods, contact [Courier Support](mailto:support@courier.com) to discuss extended retention options.
## Troubleshooting Delivery Issues
If a message shows **Sent** but the recipient hasn't received it, use the timeline in the log detail view to identify where the problem is:
1. **Check the provider response.** Click the Sent event to see the raw response. If the provider returned an error, the details will be here.
2. **Check your provider's dashboard.** Cross-reference the message ID with your provider's activity feed (SendGrid Activity, Postmark Activity, SES console, etc.) for bounce, block, or deferral events.
3. **Verify sender authentication.** Confirm that SPF, DKIM, and DMARC records are correctly configured for your sending domain.
If the message shows **Delivered** but the recipient still hasn't received it, the issue is on the recipient's side; their mail server accepted the email, but an internal filter or email security gateway may be quarantining it.
For a full walkthrough, see [How to Debug Email Delivery Issues](/docs/tutorials/monitoring/how-to-debug-delivery-issues).
## Automation Logs
The [Automation](/docs/platform/automations/automations-overview) logs provide detailed insights into the status of an automation run as well as step details.
### Automation Run List
Clicking into one of the automation in log opens the run summary, providing details on each of the steps in the automation.
# Audience Trigger for Automations
Source: https://www.courier.com/docs/platform/automations/audience-trigger
Trigger an Automation run for each user entering or leaving a dynamic audience. Add a GET Profile step to load fields before personalized sends.
## Overview
The Audience trigger starts an automation run for each user who enters or leaves a [dynamic audience](/docs/platform/users/audiences). When a user's profile changes and they newly satisfy (or no longer satisfy) the audience rules, Courier fires an individual automation run for that user.
This is useful for workflows that react to profile changes, such as:
* Sending a welcome sequence when a user upgrades to a premium tier
* Triggering a win-back flow when a user becomes inactive
* Notifying an account manager when a high-value customer changes status
## Configuration
1. Open the Automation Designer and select **Audience** as the trigger source.
2. Choose the event type:
* **Match** fires when a user enters the audience.
* **Unmatch** fires when a user leaves the audience.
3. Choose your audience from the dropdown.
Each time the event fires, Courier creates a separate automation run for that user.
## Run Context
When an audience trigger fires, the automation run context contains **only the user's ID** as the recipient. Stored profile fields (email, phone, custom attributes) and data are **not** automatically loaded.
| Context field | Value |
| ------------- | ------------------------------------- |
| `recipient` | The user ID that matched or unmatched |
| `profile` | Empty |
| `data` | Empty |
This differs from API-invoked and Segment-triggered automations, which pre-load profile data before the first step runs.
### Loading Profile Data
If your workflow uses profile fields (for example, in [If/Switch conditions](/docs/platform/automations/control-flow) or template personalization), add a [GET Profile](/docs/platform/automations/get-profile) step early in your workflow, before any step that reads profile data. The GET Profile step fetches the user's stored Courier profile and merges it into the run context.
**Audience Trigger** → **[GET Profile](/docs/platform/automations/get-profile)** → **[If/Switch](/docs/platform/automations/control-flow)** (profile condition) → **Send**
Without the GET Profile step, profile-based conditions will evaluate against an empty object and always fall through to the default branch.
## Audience Membership Timing
Audience triggers depend on Courier's [pre-computed membership model](/docs/platform/users/audiences#how-audience-membership-works). A trigger fires when membership changes, not on a fixed schedule. Keep in mind:
* **Profile updates drive membership.** A trigger fires only when a profile change causes a user to enter or leave the audience.
* **Rule changes trigger rebuilds.** If you update an audience's filter rules, Courier rebuilds the membership list. This can take several minutes for large audiences, and triggers may fire as members are re-evaluated.
* **Each match is a separate run.** If you update audience rules and many users match at once, each user gets their own automation run.
## Related Resources
Audience rules, operators, and membership model
Load profile data into the automation context
Conditional logic using profile, data, and step refs
Time-based triggers and cron expressions
# Automation Overview
Source: https://www.courier.com/docs/platform/automations/automations-overview
Courier's automation engine for building notification workflows with triggers, actions, batching, digests, and conditional logic.
Courier's automation engine lets you build notification workflows that go beyond simple message delivery. Create sequences that adapt to user behavior, batch notifications, coordinate across channels, and implement business logic.
## When to Use Automations
If you're sending straightforward transactional messages, you don't need automations. The [Send API](/docs/platform/sending/send-message) handles rendering, routing, preferences, and delivery in a single call, and that's the right choice for most notifications.
Automations are for when your notification logic involves:
* **Timing** - delays, scheduled delivery, or waiting for business hours
* **Sequences** - multi-step flows like onboarding or reminder chains
* **Batching and digests** - grouping events into a single notification
* **Conditional logic** - branching based on user data, delivery status, or external conditions
* **Cancellation** - aborting a flow when conditions change (e.g., user completes the action before a reminder fires)
Automations use the same templates, users, and preferences as direct sends. Moving a notification into an automation doesn't require changing your content or user setup. See [Choose a Sending Strategy](/docs/platform/sending/choosing-your-sending-strategy) for a full comparison.
Courier separates **content** (templates) from **logic** (automations): product teams can update templates in the designer without deploys, while engineers manage automation flow. Content changes go live instantly, with no risk to workflow logic.
## Automation Components
Automations are built from three types of nodes: **triggers** that start workflows, **actions** that perform tasks, and **control flow** that adds logic.
### Trigger Nodes
Triggers define how an automation starts. Every automation has one entry point.
| Trigger | Description |
| ----------------- | ------------------------------------------------------------------------------- |
| **API Invoke** | Start via `POST /automations/:template_id/invoke`. Default trigger type. |
| **Schedule** | Run at specific times using cron expressions, recurrence rules, or fixed dates. |
| **Segment** | React to Segment `identify`, `group`, or `track` events. |
| **Audience** | Fire when users match or unmatch an audience. |
| **Inbound Event** | Respond to events from connected sources. |
| **Webhook** | Start from external webhook calls. |
| **Digest** | Release accumulated digest events on schedule. |
See [Audience Trigger](/docs/platform/automations/audience-trigger), [Webhook Trigger](/docs/platform/automations/webhook-trigger), [Scheduling](/docs/platform/automations/scheduling), and [Segment Integration](/docs/platform/automations/segment) for configuration details.
### Action Nodes
Actions perform tasks in your workflow.
| Action | Description |
| ------------------ | -------------------------------------------------------------- |
| **Send** | Deliver a notification to a user via any channel. |
| **Send to List** | Send to all subscribers of a Courier list. |
| **Delay** | Pause execution for a duration or until a specific time. |
| **Fetch Data** | Make HTTP requests and merge response into automation context. |
| **Update Profile** | Modify a user's Courier profile. |
| **Get Profile** | Load a user profile into the automation context. |
| **Invoke** | Call another automation template. |
| **Cancel** | Cancel a running automation by its cancellation token. |
| **Add to Batch** | Accumulate events for batched delivery. |
| **Add to Digest** | Add events to a user's digest. |
| **Throttle** | Rate-limit notifications per user or globally. |
See [Steps](/docs/platform/automations/steps), [Batching](/docs/platform/automations/batching), and [Digests](/docs/platform/automations/digest) for details.
### Control Flow Nodes
Control flow adds conditional logic and branching.
| Node | Description |
| ---------- | ---------------------------------------------------------- |
| **If** | Branch based on a single condition (true/false paths). |
| **Switch** | Route to the first matching condition from multiple cases. |
| **Branch** | Multi-path routing with condition groups (V3). |
Conditions can evaluate:
* **Data** fields from the automation context
* **Profile** fields from the user profile
* **Step Ref** status of previous send steps (e.g., `CLICKED`, `DELIVERED`)
* **JS Expression** for complex logic
See [If / Switch](/docs/platform/automations/control-flow) for operators and examples.
## Common Use Cases
### User Onboarding
Create onboarding sequences that adapt to user progress:
1. Send welcome email immediately after signup
2. Wait 24 hours, then check if user completed first action
3. If not, send a reminder via push notification
4. If completed, send a congratulations message
### Batched Activity Notifications
Reduce notification fatigue by batching activity:
1. Collect events (likes, comments, mentions) as they occur
2. After 1 hour of inactivity (or max 24 hours), release the batch
3. Send a single notification summarizing all activity
See [Batching](/docs/platform/automations/batching) for configuration.
### Scheduled Digests
Send periodic summaries to users:
1. Configure a digest schedule in [Preferences Editor](https://app.courier.com/settings/preferences)
2. Add events to the digest throughout the period
3. Automation triggers at schedule time with accumulated events
4. Send personalized digest notification
See [Digests](/docs/platform/automations/digest) for setup.
### Agent-Human Collaboration
Build human-in-the-loop patterns for AI agents and automated systems:
1. Agent triggers an automation requesting human approval
2. Send an actionable notification to Inbox (approve/reject buttons)
3. Wait for user response via webhook or inbound event
4. Branch on the response: proceed, escalate, or cancel
The same pattern works for escalation with timeouts (delay, check delivery status via step ref, escalate if no response) and confirmation flows. Automations provide the timing, branching, and cancellation logic; the Send API and Inbox handle the human interaction.
## Getting Started
1. Navigate to [Automations](https://app.courier.com/automations) in the Courier app
2. Click **New Automation** to create a template
3. Add a trigger node to define how the automation starts
4. Add action nodes for each step in your workflow
5. Add control flow for conditional logic
6. **Publish** the automation to activate it
7. Invoke via API or wait for trigger conditions
For the visual designer interface, see [Designer](/docs/platform/automations/designer).
## Next Steps
Build workflows visually
All automation actions
Group notifications
Scheduled summaries
# Batching Notifications in Automations
Source: https://www.courier.com/docs/platform/automations/batching
Group automation events into one notification using inactivity period, max wait time, or item count. Supports global, user, and dynamic batch scopes.
# Batching
This reduces notification frequency and improves the likelihood of recipients engaging with the content.
Batching operates based on the following criteria:
1. **Period of inactivity**: If no events occur within a defined time span (e.g., one hour), all events since the last notification are bundled into a single notification.
2. **Maximum event count**: Notifications are held until a specified number of events (e.g., 100) have accumulated. These events are then summarized in a single notification.
3. **Maximum wait time**: All events that would have triggered notifications within a predefined duration (e.g., 24 hours) are summarized into a single notification.
Batching is supported across various channels (email, SMS, chat, in-app inbox, push) and providers (Twilio, Sendgrid, email SMTP, Slack, MS Teams, WeChat).
## Batching Example Use Cases
### Article Performance Summary
When an article is published on a news website, events are generated for each view, comment, and share.
Instead of sending a notification for each event, these events can be batched within an automation.
Once the initial surge of activity subsides or the maximum wait time is reached, the batch compiles view count, top comments, and share count data.
This summary is then sent to the author via email.
### Data Warehouse Analytics
A company processing large amounts of data may generate reports at different times.
An event is triggered upon each report's completion.
With batching, these reports can be grouped and sent in a single email once all the reports for the day are generated.
### Social Media Engagement
When a user creates a popular post that receives numerous likes within minutes, batching can be used to notify the user once the post hasn't received new likes for a specified period.
For example, "Your post received 17 likes."
## Creating A Batch
To create a batch, use the "Add to Batch" action, then click within the batch node to open the editor in the sidebar.
## Configuring A Batch
To configure a batch, click on the batch node to open the editor in the sidebar.
| Parameter | Description |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Wait Period** | Defines the period of inactivity before a batch is considered complete. If no events are received within this time frame, the batch will be finalized and sent to the next step. |
| **Max Wait** | Defines the maximum amount of time to wait before finalizing the batch. If this time frame is reached, the batch will immediately complete, even if an event had been received within the "wait period." |
| **Retain Items** | Defines the number of items to retain and send with the batch. Users can specify any positive integer value. Retention does not affect the tracked count. For example, if `Retain Items` is set to 10 and 15 items are received, the passed count will be 15, but only 10 items will be included in the batch. Options: **First *N***, **Last *N***, **Highest *N*** (requires `Sort Key`), **Lowest *N*** (requires `Sort Key`), where *N* is the user-specified number of items to retain. |
| **Sort Key** | Required when Highest or Lowest is selected for Retain Items. Defines the key of the data object to be used to determine item ordering. For example, if `Sort Key` is set to `refs.data.upvotes`, and the automation is invoked with `{ "data": { "upvotes": 5 }}`, `5` will be the value used to determine the order and position of the event in the items array. |
| **Max Items** | *(Optional)* Defines the maximum number of events to include in a batch before completion. When a batch has received this number of events, it will immediately finalize and send the batch to the next step. |
| **Scope** | Defines the scope of the batch, determining how events are grouped together. Options: **Global** (default), **User**, **Dynamic**. See [Batch Scope](#batch-scope) for more details. |
| **Category Key** | *(Optional)* Allows events to be grouped by categories. See [Working with Categories](#batch-categories) for more details. |
## Batch Scope
The `Scope` parameter in the batch node configuration allows you to define the scope of the batch.
It determines how events are grouped together within the batch.
There are three options for the batch scope:
| Scope | Description |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Global (default)** | • Events are grouped together based on the batch step ID.
• The batch will trigger and send the grouped events to the next step when either the maximum number of events is reached or the specified time period elapses. |
| **User** | • Events are grouped together based on the unique combination of `user_id` and batch step ID.
• The batch will trigger when enough events associated with a specific user are added to the batch or when the specified time period elapses, allowing for user-specific batching of events. |
| **Dynamic** | • Events are grouped together based on the `Batch Key` specified in the batch configuration, regardless of the batch step ID or automation they belong to.
• This allows for flexible event grouping based on specific criteria, but may lead to unexpected results as events from different batch steps or automations may be merged. |
To batch messages for a user while grouping them by category (such as tenant or topic), create a `Batch Key` that combines a user identifier and a category identifier. For example, use `'batch_key': 'user_id tenant_id'` and pass it to the batch node with `refs.data.batch_key`. This groups related notifications together into a single batch.
Dynamic scope behavior is subject to change in future revisions of the automation system.
## Invoking an Automation Containing a Batch
* When an automation containing a batch node is invoked, Courier checks for an ongoing batch run for the configured scope.
* **If none exists:**
1. A new batch run is created.
2. The current automation run is marked as pending.
3. The system waits until the batch is complete before proceeding to the next step.
* **If an existing batch run is found:**
1. The data object is added to the existing batch.
2. The current automation run is terminated.
3. Steps connected to the batch are not executed, ensuring that nodes following a batch node are only executed once per batch run.
```ts theme={null}
{
batch: {
// The number of events received for this category, may be different than items.length
// depending on item retention configuration.
count: number;
// An array of the data objects of each automation added to this batch.
items: any[];
}
}
```
**Example:** If a batch automation is invoked 3 times with the following values:
```js Invoke 1 theme={null}
{
data: {
like_from: "Drew";
}
}
```
```js Invoke 2 theme={null}
{
data: {
like_from: "Alex";
}
}
```
```js Invoke 3 theme={null}
{
data: {
like_from: "Abby";
}
}
```
The steps following the batch node would be provided this data object:
```js Completed Batch Data theme={null}
{
batch: {
count: 3,
items: [
{ like_from: "Drew" },
{ like_from: "Alex" },
{ like_from: "Abby" },
]
}
}
```
This data can be accessed from a notification template using variable syntax.
## Batch Categories
When the `Category Key` is set to a dynamic value such as `refs.data.category_key`, events can be grouped by category in a batch run.
In this case, the batch is aggregated by category:
**Example:** In the batch node, `Category Key` is set to `refs.data.category_key`.
Each time the automation is invoked, a `category_key` is supplied.
Given the following invocations:
```js Invoke theme={null}
{
data: {
category_key: "likes",
like_from: "Drew"
}
}
```
```js Invoke theme={null}
{
data: {
category_key: "likes",
like_from: "Alex"
}
}
```
```js Invoke theme={null}
{
data: {
category_key: "comments",
comment: "Excellent post!"
}
}
```
The automation steps following the batch node would receive this data object:
```js Completed Batch Data theme={null}
{
"batch": {
"likes": {
"count": 2,
"items": [
{ "like_from": "Drew" },
{ "like_from": "Alex" }
],
},
"comments": {
"count": 1,
"items": [
{ "comment": "Excellent post!" }
],
},
}
}
```
# Cancelling an Automation
Source: https://www.courier.com/docs/platform/automations/cancel
Cancel a running Courier Automation using a dynamic token. Add the token in settings, then invoke a second automation with a Cancel node to stop the run.
## Add a Cancellation Token
To allow an automation to be cancelled, add a cancellation token to the automation.
1. Navigate to the settings tab of the automation you want to cancel.
2. Add a cancellation token. This can be any value desired. Its best to use a dynamic value so
a specific automation invocation may be cancelled, rather than all running instances. For
example, you may use `refs.data.user_id`, which would allow the automation run to be associated
with the user the automation would send to. You can also reference profile fields with `refs.profile.*`.
3. Publish the Automation.
4. Create a new automation
5. Add the `Cancel Automation` node
6. Set the token to the same token from step 2.
7. Publish
## Canceling From the UI
An automation can also be canceled from automation logs.
1. Navigate to the [automation logs page](https://app.courier.com/logs/automations) in Courier.
2. Find your automation in the logs, you can search with a run Id or source.
3. If your automation has not finished processing, a cancel button will be shown in the run summary.
4. Clicking this button will cancel your automation.
## Canceling Multiple Automations
When multiple automations share the same cancellation token and a cancel automation is invoked, all automations that have the same token will be simultaneously canceled.
## Templated Cancellation Token
It may be advantageous to create a compound key for your cancellation token. For example, if you want to have a cancellation token that combines two different fields, or a data field and a static string. In automations, most fields support Javascript string interpolation and the cancellation token is an example of one. The documentation above says you can use `refs`, for example `refs.data.user_id` and the following would be identical in the cancellation token field, `${data.user_id}`. For more advanced interpolation you can perform nested interpolations as well.
**Static Fields**
```javascript theme={null}
${`onboarding-flow-${data.user_id}`}
```
**Combining Fields**
```javascript theme={null}
${`${data.user_id}-${data.tenant_id}`}
```
# Automation Conditional Logic: If and Switch
Source: https://www.courier.com/docs/platform/automations/control-flow
Add If and Switch nodes to Courier Automations to route workflows by data, profile fields, step status, or JavaScript expressions.
## Types of Control Flow
### If
The `If` node routes workflow execution based on a single condition. When the condition evaluates to true, the workflow follows the True branch; otherwise, it follows the False branch.
### Switch
The `Switch` node allows routing based on multiple conditions.
It evaluates conditions in order and routes to the first matching branch.
If no conditions match, the workflow follows the default branch.
## Comparison Operators
The following operators are available for Data, Profile, and Step Ref conditions:
| Operator | Description |
| ------------------------------- | -------------------------------------------------- |
| **is** (eq) | Exact match |
| **is not** (neq) | Does not match |
| **is one of** (one\_of) | Matches any value in a comma-separated list |
| **contains** | String contains substring, or array contains value |
| **does not contain** | String/array does not contain value |
| **greater than** (gt) | Numeric greater than |
| **greater than or equal** (gte) | Numeric greater than or equal |
| **less than** (lt) | Numeric less than |
| **less than or equal** (lte) | Numeric less than or equal |
| **starts with** | String starts with prefix |
| **ends with** | String ends with suffix |
## Condition Sources
The `If` and `Switch` nodes can evaluate boolean expressions from four different sources:
| Source | Description |
| ---------------- | ----------------------------------------------------------- |
| **Data** | Compare values from the automation context's data object |
| **Profile** | Compare values from the automation context's profile object |
| **Step Ref** | Monitor status of previous Send steps |
| **JS Condition** | Write custom JavaScript for complex logic |
### Data
*Compare a field within the `data` key of the automation context with a value.*
| Property | Description | Example |
| ------------ | ------------------------------ | ----------- |
| `Field` | The data object to evaluate | `data.foo` |
| `Comparison` | The comparison operator to use | `is one of` |
| `Value` | The value to compare against | `bar, baz` |
### Profile
*Compare a field within the `profile` key of the automation context with a value.*
| Property | Description | Example |
| ------------ | ------------------------------ | ----------------- |
| `Field` | The profile object to evaluate | `address.country` |
| `Comparison` | The comparison operator to use | `is` |
| `Value` | The value to compare against | `Canada` |
Profile conditions require profile data to be present in the automation context. Automations started by an [audience trigger](/docs/platform/automations/audience-trigger) or a schedule do not automatically load stored profile fields. Add a [GET Profile](/docs/platform/automations/get-profile) step before your If or Switch node so the user's profile is available for comparison.
### Step Ref
*Check the current status of an upstream Send node, using it's reference ID (Ref). The Ref value is defined in the Send node's configuration.*
| Property | Description | Example |
| ------------ | ------------------------------ | ---------------------- |
| `Ref` | The send node's status object | `welcome_email.status` |
| `Comparison` | The comparison operator to use | `is not` |
| `Value` | The value to compare against | `CLICKED` |
Possible [status](/docs/platform/analytics/message-logs#send-status-key-and-definitions) values are: `CLICKED`, `DELIVERED`, `ENQUEUED`, `FILTERED`, `OPENED`, `SENT`, `SIMULATED`, `UNDELIVERABLE`, `UNMAPPED`, `UNROUTABLE`
### JS Condition
*Use JavaScript expressions when standard comparisons aren't sufficient.*
| Property | Description | Example |
| ------------ | ------------------------------------- | -------------------------------------- |
| `Expression` | The javascript expression to evaluate | `data.expiry < (new Date()).getTime()` |
```javascript JS Condition examples theme={null}
// Transform and compare values
data.email.toLowerCase().includes('@company.com')
// Check for key existence or null values
'preferences' in data && data.preferences !== null
// Compare date diffs as ISO strings
Date.parse(data.lastLogin) < Date.now() - (30 * 24 * 60 * 60 * 1000)
// Manipulate nested arrays and objects
data.orders.filter(order => order.total > 100).length > 0
```
#### Javascript Expressions in Value Fields
Javascript expressions can also be used in the `Value` field of Data, Profile, and Step Ref sources.
```javascript Examples theme={null}
// Dynamic timestamps
${(new Date()).getTime()}
// Concatenate values
${data.firstName + ' ' + data.lastName}
// Default values
${profile.language || 'en'}
```
# Automation Debugger
Source: https://www.courier.com/docs/platform/automations/debugger
Test Courier Automation workflows in the Debugger: create a test run context, simulate an event, and inspect node-level data before going to production.
# Automation Debugger
Courier's automation debugging tool can be used to run a test event through your automation template and debug any errors you may come across.
## Creating a Test Run Context
Similar to notification templates, automations can now be tested with test run contexts passed in and saved as test events. These tests can mimic an incoming payload passed through [Segment](/docs/platform/automations/segment), [inbound events](/docs/platform/automations/inbound-events), or an [adhoc automation call](/docs/platform/automations/steps). You can create a test event from the `Debug` tab in your automation template.
## Running The Test Run Context
With your automation test event configured, you can execute a test run through the debugging tool to simulate the event passed through the automation workflow. When ready, click on the `Run Again` button and select each node to see the data being passed.
### Verifying Test Data
After running your simulated run, you can select each node in your workflow to verify that the data is passed through to each consecutive node. This can be helpful if you have several nodes that rely on incoming data payloads.
## Run your Automation
After testing with the debugger, invoke your automation via API with the data payload you want to pass.
`POST https://api.courier.com/automations/:template_id/invoke`
```json theme={null}
{
"data": {
"order_id": "ORD-12345",
"status": "shipped"
},
"profile": {
"email": "user@example.com",
"phone_number": "+12345678910",
"name": "Jane"
}
}
```
The automation run context shows a successful run with the data you passed.
## Testing GET Profile Nodes
Use the debugger to test a [GET Profile](/docs/platform/automations/get-profile) node before promoting your template to production. The debugger shows the run context passed down your automation after fetching a profile.
## Related Resources
Build workflows visually
Access and map data in automations
# Automations Designer
Source: https://www.courier.com/docs/platform/automations/designer
Build notification workflows in the Automations Designer. Add trigger and action nodes, connect them sequentially, and invoke via API or in-app test.
## Creating an Automation
Start by navigating to the [automations page within the Courier
app](https://app.courier.com/automations). From there, a new automation template can be created by
clicking the `New Automation` Button.
## Renaming an Automation
To rename a template, click the name in the upper left hand corner of the designer. By default, the
name is "Untitled Automation". The name is automatically saved after being updated.
## Working with Automation Nodes
A new template starts with a placeholder trigger node. A trigger node defines what starts an automation run. The trigger is optional; you can always invoke a template directly via `POST /automations/:template_id/invoke`.
To add nodes, expand from the list on the left and then click and drag a node onto the right. The first trigger and action node will snap into their respective placeholders:
Nodes are executed sequentially through their connections, from top to bottom. To connect two nodes, click and drag from the white dot at the bottom of the source node to the top of the target node:
## Automation Invocation
Be sure to publish the template before invoking (using the publish button on the upper right).
There are a few ways to invoke a template, either directly in the automation template, through a trigger node, or through an api call.
See [Scheduling automations](/docs/platform/automations/scheduling) or [Triggering With Segment](/docs/platform/automations/segment) to get
an understanding of how trigger nodes work.
### Difference View
When publishing a template, the automation designer will prompt the user with a difference view confirmation showing the changes between versions. You can confirm the changes on this window.
### Invoking an Automation using the API
To run an automation by api call, grab the automation template id or the alias from the settings section of
the designer and invoke it by calling POST `api/automations/:template_id_here/invoke`. View
[Automations API docs](/docs/api-reference/automations/invoke-an-automation) for
more details.
### Invoking an Automation from the Designer
You can invoke the automation template directly from the designer in the **Invoke** tab. This can be a good way to test the automation or to invoke very simple automation templates.
*Note:* Invoking from the designer will pass an empty data payload `{}` to the automation run
## Related Resources
All automation action types
Test automations before production
Schedule automations with cron, recurrence, or one-time triggers
Invoke automations via API
# Automation Digests
Source: https://www.courier.com/docs/platform/automations/digest
Aggregate user events into scheduled batch notifications. Configure digest schedules, categories, and retention in Preferences; wire up two automations.
Courier Automations can create recurring user digests on configured intervals. This is useful for weekly reports, activity summaries, or any periodic notification where you want to aggregate events.
Automation digests require **two separate automations**: one to collect events (with an "Add to Digest" node) and one to release the digest (with a "Digest" trigger). Events won't be delivered if either automation is missing. See [Working with Digests in Automations](#working-with-digests-in-automations) for setup details.
For simpler use cases, Courier offers a [digest feature in the Send API](/docs/platform/sending/digest-send) that doesn't require automations.
## Configure Digests in the Preferences Editor
Configure a digest with at least one schedule in the [Preferences Editor](https://app.courier.com/settings/preferences). Create a new subscription topic or open an existing one's settings.
### Schedule a Digest
In the digest section, click **Schedules** to define when the digest releases. At least one schedule is required, but you can configure multiple to give users a choice. Multiple schedules appear as options in the hosted preferences page.
The date-time picker uses local timezone. Users viewing hosted preferences or using front-end preference components see times in their local timezone.
### Schedule ID Format
When working with digest APIs, schedule IDs use the format `sch/{uuid}`. You can find the schedule ID in the browser's network panel when viewing the Schedules section of a subscription topic - look for the `pk` field in the response.
The schedule ID includes the `sch/` prefix. When using this ID in API URLs, you must URL-encode the forward slash as `%2F`.
**Examples**:
| Raw Schedule ID | URL-Encoded for API |
| ------------------------------------------ | -------------------------------------------- |
| `sch/a3726ea9-3453-465f-93ad-632061ba8f59` | `sch%2Fa3726ea9-3453-465f-93ad-632061ba8f59` |
| `sch/549ef749-7c87-4eea-b695-2e2d23c025ff` | `sch%2F549ef749-7c87-4eea-b695-2e2d23c025ff` |
### Configure Digest Categories
Categories separate different types of data within the same digest. For example, a weekly blog engagement digest might have separate categories for likes and comments.
Each category has a `retain` setting that controls which events are kept when the digest releases:
| Retain Option | Description |
| -------------- | ------------------------------------------ |
| **First 10** | The first 10 events received (default) |
| **Last 10** | The most recent 10 events |
| **10 Highest** | Top 10 sorted by `sort_key` (descending) |
| **10 Lowest** | Bottom 10 sorted by `sort_key` (ascending) |
When using **Highest** or **Lowest**, you must specify a `sort_key` (a data attribute in the event used for sorting).
When the digest releases, each category is delivered in this format:
```json theme={null}
{
"[category_key]": {
"count": 15,
"items": [...]
}
}
```
* `count`: Total number of events received (may exceed 10)
* `items`: The retained events based on your retain setting (max 10)
### Configure Other Digest Settings
The **Invoke when empty** toggle sends digests to users in an audience even if no events were received during the schedule window. This is useful for fetching custom data in the automation and sending it regardless of event activity.
## Mapping a Template to a Digest
Link a notification template to the digest so all accumulated events trigger through an automation.
From the digest settings in preferences, map the notification template you want to use.
Then configure an automation with the appropriate trigger. For example, if digest events come from Segment:
After publishing, digested events appear in the logs until the schedule releases the digest.
## Working with Digests in Automations
Two automation nodes handle digest events: one to collect events, one to release them.
### Digest Event Collection
Add an **Add to Digest** node to your automation:
1. Right-click the canvas and select "Add to Digest"
2. Choose the subscription topic where you configured the digest
3. Events accumulate per user, identified by `user_id` or `userId` in the event data
Both `user_id` and `userId` are accepted in the event data for user identification.
### Digest Release
Create a separate automation to process and send the compiled digest:
1. Select the trigger node and choose **Digest** from the Trigger section
2. Select the same subscription topic used for event collection
3. The automation fires at scheduled intervals, processing each user's accumulated data
The digest payload arrives in this structure:
```json theme={null}
{
"[category_key]": {
"count": 15,
"items": [
{ "event": "data", "from": "first_event" },
{ "event": "data", "from": "second_event" }
]
}
}
```
Use this data in your notification template to render the digest summary.
## Testing Digests
1. **Send test events** by invoking your collection automation (see [Adding Events via API](#adding-events-via-api))
2. **Verify accumulation** by [listing digest instances](#listing-digest-instances); confirm `event_count` increments and `status` is `IN_PROGRESS`
3. **Trigger a release** using the [manual trigger endpoint](#releasing-a-digest-early); check that your template receives the digest payload
4. **Verify the email** contains the expected digest items and category data
Do not manually invoke digest trigger automations via the `/automations/{id}/invoke` endpoint. This bypasses the digest accumulation system and will result in empty digest data. Always use the `/digests/schedules/{id}/trigger` endpoint or wait for the scheduled release.
## Timing Considerations
### List Subscriptions and Digest Events
If your automation subscribes a user to a list and then immediately sends a digest event, the subscription may not have propagated before the event is processed. This can cause the event to be dropped because the user isn't yet recognized as a subscriber for the topic.
To avoid this race condition:
* Add a short delay (a few seconds) between the subscription call and the first digest event
* Or subscribe users to lists as a separate, earlier step in your workflow rather than inline with digest event submission
### Scheduled vs Instant Delivery
Scheduled digests batch events until the next scheduled release time. If you send a digest event at 9:05 AM and the schedule is "Daily at 9:00 AM," that event won't be delivered until the following day at 9:00 AM. This is expected behavior; the event is accumulating in a new digest instance.
To test digests without waiting for the schedule, use the [manual trigger endpoint](#manually-triggering-a-digest-release) or add a shorter schedule (every 5-15 minutes) during development.
## Managing Digests via API
If the Preferences Editor UI is unavailable or you need programmatic control over digest schedules, you can manage digests through the API.
### Listing Digest Instances
Check which users have accumulated events for a given schedule:
```bash theme={null}
curl -X GET "https://api.courier.com/digests/schedules/sch%2F{uuid}/instances" \
-H "Authorization: Bearer $COURIER_API_KEY"
```
Each instance in the response includes `event_count`, `status` (`IN_PROGRESS` or `CLOSED`), and the `user_id`.
### Releasing a Digest Early
Trigger an immediate release without waiting for the scheduled time:
```bash theme={null}
curl -X POST "https://api.courier.com/digests/schedules/sch%2F{uuid}/trigger" \
-H "Authorization: Bearer $COURIER_API_KEY"
```
This closes all `IN_PROGRESS` instances for the schedule and invokes the digest trigger automation for each user.
### Adding Events via API
You can submit digest events by invoking the collection automation directly:
```bash theme={null}
curl -X POST "https://api.courier.com/automations/{collector-automation-id}/invoke" \
-H "Authorization: Bearer $COURIER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": {
"user_id": "user-123",
"title": "New comment on your post",
"body": "Alice replied to your thread"
}
}'
```
Use the collection automation's ID (the one with the "Add to Digest" node), not the trigger automation's ID. Invoking the trigger automation directly bypasses the digest system and produces empty results.
# Accessing Dynamic Data in Automations
Source: https://www.courier.com/docs/platform/automations/dynamic
Access trigger payload values in Automation steps via the refs object. Remap nested attributes to cleaner names and interpolate into Fetch Data URLs.
The `refs` object has access to the following properties:
* `brand`
* type: string
* description: A unique identifier that represents the brand that should be used for rendering the notification.
* `data`
* type: object
* description: An object that includes any data you want to pass to a message template or accessor type. The data will populate the corresponding template variables.
* `profile`
* type: object
* description: an object that includes any key-value pairs required by your chosen Integrations (see our Provider Documentation for the requirements for each Integration.) If profile information is included in the request and that information already exists in the profile for the recipientId, that information will be merged.
* `template`
* type: string
* description: A unique identifier that can be mapped to an individual Notification. This could be the "Notification ID” on Notification detail pages (see the Notifications page in the Courier app) or a custom string mapped to the event in settings.
* `recipient`
* type: string
* description: A unique identifier associated with the recipient of the delivered message, which can optionally map to a Courier managed profile.
These properties can all be set using the `invoke` api. Additionally, the data property can be initialized by triggers
such as Segment, or modified by actions such as `Fetch Data`.
For example, you can access the `userId` supplied by segment in a send node using `refs.data.userId`
## Mapping Dynamic Data to Variables
By default, all `data` passed into an automation is passed through to the notification template in a send call. However, there are cases where you want to map specific data attributes to different variables.
1. Open a send node, click on the advanced section, and then click edit next to the `Data` field
2. You can either use the interactive JSON editor, or just modify the JSON in the text editor directly. The syntax to map the attribute is as follows:
```json theme={null}
"": {
"$ref": "data."
}
```
### Example: Mapping Dynamic Data to a Variable
Let's say we have a segment event as a trigger for our automation. The event has a `vendor_name` attribute in the segment `properties` object and we want to map that to a `vendor` attribute in our send call.
We would use the following syntax:
```json theme={null}
"vendor": {
"$ref": "data.properties.vendor_name"
}
```
In the notification template, you are now able to access the `vendor` property directly in a template variable `{vendor}` rather than destructing the data object `{data.properties.vendor_name}`
## URL Interpolation
You can use dynamic data to construct URLs by interpolating values directly into the URL string. This is particularly useful for Fetch Data steps where you need to make API calls with dynamic parameters.
Wrap the entire URL with template literal syntax using `${`...`}` and use `${refs.data.variableName}` to substitute values within the URL string.
### Basic URL Interpolation
```json theme={null}
{
"type": "fetch",
"url": "${`https://api.courier.com/profiles/${refs.data.userID}`}",
"method": "get"
}
```
### Advanced URL Interpolation Examples
**User-specific API calls:**
```json theme={null}
{
"type": "fetch",
"url": "${`https://api.example.com/users/${refs.data.userId}/orders/${refs.data.orderId}`}",
"method": "get"
}
```
**Profile-based endpoints:**
```json theme={null}
{
"type": "fetch",
"url": "${`https://api.example.com/accounts/${refs.profile.accountId}/settings`}",
"method": "get"
}
```
**Query parameters with interpolation:**
```json theme={null}
{
"type": "fetch",
"url": "${`https://api.example.com/search?q=${refs.data.searchTerm}&limit=${refs.data.limit}`}",
"method": "get"
}
```
**Nested data access:**
```json theme={null}
{
"type": "fetch",
"url": "${`https://api.example.com/events/${refs.data.event.id}/attendees`}",
"method": "get"
}
```
### URL Interpolation with Authentication
Combine URL interpolation with authentication headers:
```json theme={null}
{
"type": "fetch",
"url": "${`https://api.example.com/users/${refs.data.userId}/profile`}",
"method": "get",
"headers": {
"Authorization": "Bearer ${refs.data.accessToken}",
"X-User-ID": "${refs.data.userId}"
}
}
```
**URL Encoding**: Courier automatically handles URL encoding for interpolated values. Special characters in your data will be properly encoded in the final URL.
## Reserved Field values
The following field values are reserved for current and future interpolation features. They can
not be used literally in an input field.
* `refs`
* `data`
* `profile`
# Fetch Data Automation Step
Source: https://www.courier.com/docs/platform/automations/fetch-data
Make authenticated GET or POST requests in a Courier automation and merge the response into run context. Supports bearer tokens, API keys, and merges.
The Fetch-Data automation step can be used to make GET and POST HTTP calls from an automation workflow. The data fetched will be automatically merged into the automation's run context unless the merge strategy is explicitly defined.
Please note that requests are required to be made in `https`
To start a fetch-data step, add it to your automation canvas and configure your request URL.
## Merge Strategy
Incoming payloads must be shaped as `objects`. Arrays are not supported.
The merge strategy gives users an option on how they want the data to be populated.
* `replace`
* overwrite all properties in the Automation Cache with the http response. Removes all properties in the Automation Cache that do not exist in the http response.
* `soft-merge`
* only overwrite properties in the Automation Cache with the http response properties that do not yet exist in the Automation Cache.
* `overwrite`
* overwrite all properties in the Automation Cache with the properties from the http response.
* `none`
* do not make any changes to the Automation Cache if the Automation Cache already exists and has data. Otherwise initialize the Automation Cache.
## Authentication Patterns
The Fetch Data step supports various authentication methods for securing your HTTP requests. Here are the most common patterns:
### 1. Bearer Token Authentication
Use static bearer tokens for simple authentication:
```json theme={null}
{
"type": "fetch",
"url": "https://api.example.com/data",
"method": "get",
"headers": {
"Authorization": "Bearer YOUR_TOKEN_HERE"
}
}
```
### 2. Dynamic Bearer Token (from context)
Reference bearer tokens from your automation context:
```json theme={null}
{
"type": "fetch",
"url": "https://api.example.com/data",
"method": "get",
"headers": {
"Authorization": {
"$ref": "data.bearer_token"
}
}
}
```
### 3. Basic Authentication
Use HTTP Basic Authentication with base64-encoded credentials:
```json theme={null}
{
"type": "fetch",
"url": "https://api.example.com/data",
"method": "get",
"headers": {
"Authorization": "Basic dXNlcjpwYXNzd29yZA=="
}
}
```
### 4. API Key Authentication
Use custom API key headers:
```json theme={null}
{
"type": "fetch",
"url": "https://api.example.com/data",
"method": "get",
"headers": {
"X-API-Key": {
"$ref": "data.api_key"
}
}
}
```
### 5. Custom Headers with Multiple Auth Elements
Combine multiple authentication elements:
```json theme={null}
{
"type": "fetch",
"url": "https://api.example.com/data",
"method": "post",
"headers": {
"Authorization": {
"$ref": "data.auth_token"
},
"X-Client-ID": {
"$ref": "profile.client_id"
},
"Content-Type": "application/json"
}
}
```
### 6. JavaScript Interpolation (V2 Automation)
Use JavaScript expressions for dynamic values:
```json theme={null}
{
"type": "fetch",
"url": "https://api.example.com/data",
"method": "get",
"headers": {
"Authorization": "${`Bearer ${data.access_token}`}",
"X-Timestamp": "${Date.now()}"
}
}
```
## Security Features
### URL Validation
Courier automatically validates URLs for security. Invalid URLs will cause the step to be skipped with an "Invalid URL" status.
### Request Timeout
All fetch requests have a fixed 10-second timeout to prevent hanging requests. Requests that exceed this timeout will automatically fail with proper error handling.
### Error Handling
Failed requests are automatically logged and tracked. The automation will continue processing subsequent steps unless explicitly configured otherwise.
## Best Practices
1. **Store sensitive tokens in run context data** rather than hardcoding them in your automation configuration
2. **Use step references** for authentication flows that require token refresh
3. **Implement proper error handling** in subsequent steps to handle authentication failures
4. **Validate URLs** when possible for security
5. **Use appropriate merge strategies** to avoid overwriting important context data
6. **Test authentication flows** thoroughly in your automation development environment
## Accessing the Fetched Run Context
Once data is fetched, it will be populated in the data payload for subsequent steps to reference.
The incoming data payload can be seen in the automation logs run context.
The final rendered result references the data that was fetched in the automation workflow run.
# Get Profile Node in Automations
Source: https://www.courier.com/docs/platform/automations/get-profile
GET Profile node loads a user profile into automation context as refs.profile. Required for Audience and Schedule triggers that skip auto-loading profiles.
## Overview
The GET Profile node fetches a user's Courier profile and attaches it to the automation run context. After this node runs, profile data is available via `refs.profile` in subsequent steps.
Set the user ID to a dynamic value from the data object, such as `refs.data.user_id`.
### When You Need This Step
Not all trigger types automatically load the user's stored profile into the automation context. Use a GET Profile step when your workflow relies on profile fields (for example, in [If/Switch conditions](/docs/platform/automations/control-flow) or template personalization) and the automation is started by one of these triggers:
| Trigger | Profile auto-loaded? | GET Profile needed? |
| -------------------------------------------------------- | -------------------- | ------------------- |
| API invoke (`/automations` or `/journeys`) | Yes | No |
| Segment | Yes | No |
| [Audience match](/docs/platform/automations/audience-trigger) | No | Yes |
| Schedule | No | Yes |
Place the GET Profile node early in your workflow, before any step that reads profile data.
**Template expressions in step fields:** Many automation fields accept values wrapped in `` `${...}` `` so you can reference the run context (for example `recipient` or fields under `refs`). See [Accessing Dynamic Data](/docs/platform/automations/dynamic) for how this works across steps.
**Audience and Schedule triggers:** The user id for the run is on `recipient`, not in `data`. In the GET Profile **User ID** field, use `` `${recipient}` `` so this step loads the profile for the user that triggered the run.
### Ad Hoc Usage
Use the `get-profile` step in an [ad hoc automation](/docs/platform/automations/steps) to load a user's profile before sending:
```json theme={null}
{
"automation": {
"steps": [
{
"action": "get-profile",
"user_id": "user_123",
"merge_strategy": "none"
},
{
"action": "send",
"template": "order-update",
"recipient": "user_123"
}
]
}
}
```
### Merge Strategy
After the `get-profile` step, the user's stored profile fields (email, phone, custom attributes) are available in the automation context. The `merge_strategy` field controls how fetched data combines with existing context data (defaults to `soft-merge`):
| Strategy | Behavior |
| ------------ | ----------------------------------------------------------------------------------- |
| `soft-merge` | Merge fetched fields into existing context; existing fields are preserved (default) |
| `replace` | Replace the entire context path with fetched data |
| `overwrite` | Overwrite all properties from fetched data |
| `none` | Do not modify context if the path already has data |
See [Fetch Data](/docs/platform/automations/fetch-data) for more on merge strategies.
# Inbound Event Triggers for Automations
Source: https://www.courier.com/docs/platform/automations/inbound-events
Trigger Courier Automations from CourierJS, the REST inbound API, Segment track and identify events, or RudderStack—via dedicated trigger nodes.
## Courier Inbound Events
The [CourierJS SDK](https://github.com/trycourier/courier-js/tree/main/packages/courier-js) interfaces with the inbound track API to send client-side application events
to Courier that can be used in Automations trigger nodes.
You can also send events directly via the REST API:
```bash theme={null}
curl --request POST \
--url https://api.courier.com/inbound/courier \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"event": "order-placed",
"type": "track",
"userId": "user_123",
"properties": {
"order_id": "ORD-98765",
"total": 59.99
}
}'
```
The `event` name matches the trigger node's event filter in your automation. The `properties` object is available in the automation context via `refs.data`.
## Segment
With a [Segment third-party integration](/docs/external-integrations/cdp/segment) both [`track`](https://segment.com/docs/connections/spec/track/)
and [`identify`](https://segment.com/docs/connections/spec/identify/) are automatically ingested using the Segment trigger node in Automations.
Refer to [Segment use cases](/docs/platform/automations/segment) for more information.
## Rudderstack
With an existing [RudderStack third-party integration](/docs/external-integrations/cdp/rudderstack) both [`track`](https://www.rudderstack.com/docs/event-spec/standard-events/track/)
and [`identify`](https://www.rudderstack.com/docs/event-spec/standard-events/identify/) are automatically ingested into the Rudderstack trigger node in Automations.
## Related Resources
Trigger automations from Segment events
Trigger automations from external webhooks
# Scheduling Automations
Source: https://www.courier.com/docs/platform/automations/scheduling
Schedule Courier Automations with one-time, recurring, or cron triggers. Publish the automation to activate—no extra API call; fires within ~12 minutes.
* **One-time:** Set the specific time and date to invoke the automation.
* **Recurrence:** Identify a repeat schedule for the automation to be invoked. This is similar to scheduling a repeat event in a calendar application.
* **Cron:** Similar to Recurrence but with more specificity using [crontab expressions](https://crontab.guru/).
Because scheduling is a part of Automations, it can be used across channels (eg. email, SMS, chat, in-app inbox, push) and providers (eg. Twilio, Sendgrid, email SMTP, Slack, MS Teams, WeChat).
## Setup Scheduling
To setup a scheduled automation, drag over the Schedule trigger from the triggers section onto the canvas. Publishing an automation template is sufficient to allow the trigger to invoke the automation on the provided schedule. You do not need to make a separate API call to invoke.
The date picker uses your browser's timezone by default. You can change it using the timezone dropdown. After configuration, the displayed date remains relative to your browser timezone.
The schedule trigger is accurate to within \~12 minutes of the selected time.
## Related Resources
Build workflows visually
Trigger automations from Segment events
# Segment Automation Trigger
Source: https://www.courier.com/docs/platform/automations/segment
Trigger Courier Automations from Segment track events. Use cancellation tokens to cancel a pending notification when a follow-up event arrives.
## Segment Automation Trigger Example
In this example, we'll imagine an e-commerce site that sends out an email when a user successfully
checks out, or a reminder to finish checking out when they visit the page, but do not finish their
purchase.
For this, we'll need two automations. One for the reminder condition, the other for the checkout
success condition. We'll start with the reminder condition.
1. Create a new automation
2. Select the Segment trigger and choose correct `track` event. In our case, that is
"Checkout Page Visited". Note: The track event dropdown is populated with events Courier has
already received from Segment.
3. The reminder should be sent out a couple of hours after the checkout page was visited, so
add a delay node with the desired delay time
4. Connect the delay node to a new send node and select the desired template. We set the userId to
`refs.data.userId`, which is the userId sent to us by segment.
5. If the user successfully checks out, we won't want to send this notification. We'll add a
cancellation token so we can cancel this run in from our next automation. Navigate to the
settings tab and set `cancellation` to `refs.data.userId`. This way we can cancel any instance
of this automation associated with the same user the notification would otherwise be sent to.
6. Click publish to finalize the template
The next automation will cancel the reminder automation from above, and send an invoice on
successful checkout.
1. Create a new automation.
2. Add a Segment trigger node with a track event set to "Checkout Complete"
3. Add a cancel automation node and set the token to the user id. This will prevent the reminder
to finish checking out from sending.
4. Send the Checkout Complete notification
# Ad Hoc Automation Steps
Source: https://www.courier.com/docs/platform/automations/steps
Define Courier automation workflows inline in a single API call—no saved template needed. Full reference for batch, delay, fetch, send, cancel, and invoke.
Ad hoc automations let you define workflows in a single API request without a saved template. Each step has an `action` field that specifies its type.
For visual designer documentation, see [Batching](/docs/platform/automations/batching), [Digests](/docs/platform/automations/digest), [Throttle](/docs/platform/automations/throttle), and [Control Flow](/docs/platform/automations/control-flow).
## Add to Batch
Add the data object to a batch. Steps after "add-to-batch" are not executed until the batch conditions are met and released. Subsequent steps are invoked only once per batch run.
### Fields
| Field | Type | Required | Description |
| ----------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| action | string | required | The type of element. For the add to batch Automation Step, this value should be `"add-to-batch"`. |
| batch\_key | string | required | A unique id for the batch. This value must be the same for each event expected to be added to the batch |
| wait\_period | string | required | Defines the period of inactivity before the batch is released. Specified as an ISO 8601 duration, [https://en.wikipedia.org/wiki/ISO\_8601#Durations](https://en.wikipedia.org/wiki/ISO_8601#Durations) |
| max\_wait\_period | string | required | Defines the maximum wait time before the batch should be released. Must be less than wait period. Maximum of 60 days. Specified as an ISO 8601 duration, [https://en.wikipedia.org/wiki/ISO\_8601#Durations](https://en.wikipedia.org/wiki/ISO_8601#Durations) |
| max\_items | number | | If specified, the batch will release as soon as this number is reached |
| retain | object | required | Defines what items should be retained and passed along to the next steps when the batch is released |
| retain.type | string | required | One of `first`, `last`, `highest`, `lowest`. |
| retain.count | number | required | Currently, only 10 is supported |
| retain.sort\_key | string | | Defines the data value `data[sort_key]` that is used to sort the stored items. Required when type is set to `highest` or `lowest`. |
| category\_key | string | | Defines the field of the data object the batch is set to when complete. Defaults to `batch` |
### Schema
```json theme={null}
{
"action": "add-to-batch",
"wait_period": "",
"max_wait_period": "",
"max_items": 10,
"retain": {
"type": "first" | "last" | "highest" | "lowest",
"count": 10,
"sort_key": ""
},
"category_key": ""
}
```
### Overriding Batches In Flight
When making a batch call, you can make changes to the batching settings to a batching automation mid-flight.
**Retain Settings**
Users can initially set the `retain` parameters when invoking a batch for the first time and make changes to the `retain.count` on subsequent calls. The automation batch will be overwritten with the new retention policy. For example, if you send a batch to retain the first 10 items, and then edit the automation to retain the first 20, the batch will not release until the first 20 are retained while it's less than the `max_items` parameter.
**Send Step**
Send steps cannot be overwritten mid-flight in a batch. The first send step method introduced in the batch will run before the overwritten one takes place.
## Cancel
Cancel an Automation Run that is In Progress.
### Fields
| Field | Type | Required | Description |
| ------------------ | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| action | string | required | The type of element. For the Cancel Automation Step, this value should be `"cancel"`. |
| cancelation\_token | string | | The string that is associated with the cancelable automation run. An Automation Run is cancelable if it has a `cancelation_token` value at execution time. |
| if | string | | A boolean expression whose value is used to determine the execution of the step. Can optionally consume step reference data. |
| ref | string | | A pointer to step and its data. |
### Schema
```json theme={null}
{
"action": "cancel",
"cancelation_token": "",
"if": "",
"ref": ""
}
```
## Delay
Wait a duration of time, before proceeding to the next Automation Step.
### Fields
| Field | Type | Required | Description |
| -------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
| action | string | required | The type of element. For the Delay Automation Step, this value should be `"delay"`. |
| duration | string | | The human readable time duration. A duration value and unit is required, e.g. 5 minutes, 1 hour, 3 days |
| if | string | | A boolean expression whose value is used to determine the execution of the step. Can optionally consume step reference data. |
| ref | string | | A pointer to step and its data. |
| until | string | | The ISO 8601 timestamp that describes the length of the delay. |
Either `until` or `duration` is required.
### Schema
```json theme={null}
{
"action": "delay",
"if": "",
"ref": "",
"until": "",
"duration": ""
}
```
## Fetch Data
Fetch data via https and write the response to the `data` property of the Automation Run Cache.
### Fields
| Field | Type | Required | Description |
| ------------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| action | string | required | The type of element. For the Fetch Data Automation Step, this value should be `"fetch-data"`. |
| idempotency\_expiry | string | | A unix epoch timestamp (seconds) or an ISO\_8601 date string that describes how long the idempotency\_key is valid. |
| idempotency\_key | string | | A unique value generated by the client which the server uses to recognize subsequent retries of the same request. |
| if | string | | A boolean expression whose value is used to determine the execution of the step. Can optionally consume step reference data. |
| merge\_strategy | enum | | `replace`, `soft-merge`, `overwrite`, or `none`. If nothing is specified then the default strategy is `replace`. The strategy to merge the webhook response into the Automation Run Cache. Details below. |
| ref | string | | A pointer to step and its data. |
| webhook | object | | The webhook configuration of the resource that is accessible via http. See the `webhook` schema bellow. |
The `merge_strategy` property can be any of the following values:
* `replace`
* overwrite all properties in the Automation Cache with the http response. Removes all properties in the Automation Cache that do not exist in the http response.
* `soft-merge`
* only overwrite properties in the Automation Cache with the http response properties that do not yet exist in the Automation Cache.
* `overwrite`
* overwrite all properties in the Automation Cache with the properties from the http response.
* `none`
* do not make any changes to the Automation Cache if the Automation Cache already exists and has data. Otherwise initialize the Automation Cache.
The `webhook` can be configured with any of the following properties:
```json theme={null}
{
"webhook": {
"body": {}, // optional
"headers": {}, // optional
"params": {}, // optional
"method": "GET" | "POST", // optional, default GET
"url": ""
}
}
```
### Schema
```json theme={null}
{
"action": "fetch-data",
"idempotency_expiry": "",
"idempotency_key": "",
"if": "",
"merge_strategy": "replace" | "none" | "overwrite" | "soft-merge",
"ref": "",
"webhook": {
"body": {}, // optional
"headers": {}, // optional
"params": {}, // optional
"method": "GET" | "POST", // optional, default GET
"url": ""
}
}
```
## GET Profile
Bring a profile stored with Courier into scope. See [GET Profile](/docs/platform/automations/get-profile) for usage details.
### Fields
| Field | Type | Required | Description |
| --------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
| action | string | required | The type of element. For the GET Profile step, this value should be `"get-profile"`. |
| user\_id | string | required | The user id of the profile to load |
| path | string | | Where the profile should be attached. Can be `profile` or `data.`. Defaults to `profile`. |
| merge\_strategy | string | | How to merge the profile with existing context data. `"replace"`, `"none"`, `"overwrite"`, or `"soft-merge"` (default). |
| tenant\_id | string | | Load profile with tenant-specific data |
| if | string | | A boolean expression whose value is used to determine the execution of the step. Can optionally consume step reference data. |
| ref | string | | A pointer to step and its data. |
### Schema
```json theme={null}
{
"action": "get-profile",
"user_id": "",
"path": "profile",
"merge_strategy": "soft-merge",
"if": "",
"ref": ""
}
```
## Invoke
Invoke another Automation Template
### Fields
| Field | Type | Required | Description |
| -------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| action | string | required | The type of element. For the Invoke Automation Step, this value should be `"invoke"`. |
| context | string | | The initial values of the Automation Run Cache to invoke the Automation Template with. |
| if | string | | A boolean expression whose value is used to determine the execution of the step. Can optionally consume step reference data. |
| ref | string | | A pointer to step and its data. |
| template | string | required | A unique identifier that can be mapped to an Automation Template. This could be the Template ID or the Alias from the Automation Template Designer. |
### Schema
```json theme={null}
{
"action": "invoke",
"context": {
"brand": "",
"data": {},
"profile": {},
"template": "",
"recipient": ""
},
"if": "",
"ref": "",
"template": ""
}
```
## Send (Legacy)
Send a Notification Template
### Fields
| Field | Type | Required | Description |
| ------------------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| action | string | required | The type of element. For the Send Automation Step, this value should be `"send"`. |
| recipient | string | required | A unique identifier associated with the recipient of the delivered message. |
| template | string | required | A unique identifier that can be mapped to an individual Notification. This could be the "Notification ID” on Notification detail pages (see the Notifications page in the Courier app) or a custom string mapped to the event in settings. |
| brand | string | | The brand id to be used in the notification. |
| data | string | | An object that includes any data you want to pass to a message template. The data will populate the corresponding template variables. |
| if | string | | A boolean expression whose value is used to determine the execution of the step. Can optionally consume step reference data. |
| idempotency\_expiry | string | | A unix epoch timestamp (seconds) or an ISO\_8601 date string that describes how long the idempotency\_key is valid. |
| idempotency\_key | string | | A unique value generated by the client which the server uses to recognize subsequent retries of the same request. |
| override | string | | An object that is merged into the request sent by Courier to the provider to override properties or to gain access to features in the provider API that are not natively supported by Courier. |
| profile | string | | An object that includes any key-value pairs required by your chosen Integrations (see our Provider Documentation for the requirements for each Integration.) If profile information is included in the request and that information already exists in the profile for the recipientId, that information will be merged. |
| ref | string | | A pointer to step and its data. |
### Schema
```json theme={null}
{
"action": "send",
"brand": "",
"data": {},
"if": "",
"idempotency_expiry": "",
"idempotency_key": "",
"override": {},
"profile": {},
"recipient": "",
"ref": ""
}
```
## Send
Send a Notification Template
### Fields
| Field | Type | Required | Description |
| ------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| action | string | required | The type of element. For the Send Automation Step, this value should be `"send"`. |
| message | object | required | The Courier Message. For more information see [The Courier Message](/docs/api-reference/send/send-a-message) in the /send API reference. |
### Schema
```json theme={null}
{
"action": "send",
"message": {
"to": {
// profile
},
// either template or content is required but not both
"template": "",
"content": {}
}
}
```
## Send List
Send a message to each recipient in the list. Optionally fetch data for each recipient via http and merge the response into the `data` property of the Automation Run Cache.
### Fields
| Field | Type | Required | Description |
| ------------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| action | string | required | The type of element. For the Send List Automation Step, this value should be `"send-list"`. |
| brand | string | | The brand id to be used in the notification. |
| data | string | | An object that includes any data you want to pass to a message template. The data will populate the corresponding template variables. |
| data\_source | object | | The webhook configuration of the resource that is accessible via http. See the `data_source` schema bellow. |
| idempotency\_expiry | string | | A unix epoch timestamp (seconds) or an ISO\_8601 date string that describes how long the idempotency\_key is valid. |
| idempotency\_key | string | | A unique value generated by the client which the server uses to recognize subsequent retries of the same request. |
| if | string | | A boolean expression whose value is used to determine the execution of the step. Can optionally consume step reference data. |
| list | string | required | The Courier List Id. |
| override | string | | An object sent by Courier to the provider to leverage features of the provider API or to simply override properties of the provider api call. |
| template | string | required | A unique identifier that can be mapped to an individual Notification. This could be the "Notification ID” on Notification detail pages (see the Notifications page in the Courier app) or a custom string mapped to the event in settings. |
| ref | string | | A pointer to step and its data. |
The `merge_strategy` property of the steps `data_source` object, can be any of the following values:
* `replace`
* overwrite all properties in the Automation Cache with the http response. Removes all properties in the Automation Cache that do not exist in the http response.
* `soft-merge`
* only overwrite properties in the Automation Cache with the http response properties that do not yet exist in the Automation Cache.
* `overwrite`
* overwrite all properties in the Automation Cache with the properties from the http response.
* `none`
* do not make any changes to the Automation Cache if the Automation Cache already exists and has data. Otherwise initialize the Automation Cache.
The `webhook` property of the steps `data_source` object, can be configured with any of the following properties:
```json theme={null}
{
"webhook": {
"body": {}, // optional
"headers": {}, // optional
"params": {}, // optional
"method": "GET" | "POST", // optional, default GET
"url": ""
}
}
```
### Schema
```json theme={null}
{
"action": "send-list",
"brand": "",
"data": {},
"data_source": {
"webhook": {
"url": "WEBHOOK_URL",
"method": "GET",
"headers": {}
},
"merge_strategy": "replace"
},
"idempotency_expiry": "",
"idempotency_key": "",
"if": "",
"list": "",
"override": {},
"ref": "",
"template": ""
}
```
## Update Profile
Update the Courier Profile given a recipientId.
### Fields
| Field | Type | Required | Description |
| ------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| action | string | required | The type of element. For the Update Profile Automation Step, this value should be `"update-profile"`. |
| if | string | | A boolean expression whose value is used to determine the execution of the step. Can optionally consume step reference data. |
| merge | enum | | `replace`, `soft-merge`, `overwrite`, or `none`. If nothing is specified then the default strategy is `replace`. The strategy to merge new data into the recipient profile. Details below. |
| profile | string | | An object that includes any key-value pairs associated with the recipient profile |
| recipient\_id | string | | A unique identifier associated with the recipient profile you intend to update. |
| ref | string | | A pointer to step and its data. |
The `merge` property can be any of the following values:
* `replace`
* overwrite all properties in the recipient profile with the http response. Removes all properties in the recipient profile that do not exist in the http response.
* `soft-merge`
* only overwrite properties in the recipient profile with the http response properties that do not yet exist in the recipient profile.
* `overwrite`
* overwrite all properties in the recipient profile with the properties from the http response.
* `none`
* do not make any changes to the recipient profile if the recipient profile already exists and has data. Otherwise initialize the recipient profile
**Profile Merging**: When using update-profile steps, be mindful with the aforementioned merge strategies that can update an existing profile that was [created with a Segment Identify event](/docs/external-integrations/cdp/segment#identify-calls).
### Schema
```json theme={null}
{
"action": "update-profile",
"if": "",
"merge": "replace",
"profile": {},
"recipient_id": "",
"ref": ""
}
```
## Subscribe
Subscribe a user to a Courier List.
### Fields
| Field | Type | Required | Description |
| ------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
| action | string | required | The type of element. For the Subscribe Automation Step, this value should be `"subscribe"`. |
| list\_id | string | required | The ID of the Courier List to subscribe the user to. |
| recipient\_id | string | required | The user ID of the recipient to subscribe. |
| subscription | object | | Optional subscription options including preferences. |
| if | string | | A boolean expression whose value is used to determine the execution of the step. Can optionally consume step reference data. |
| ref | string | | A pointer to step and its data. |
### Schema
```json theme={null}
{
"action": "subscribe",
"list_id": "my-list-id",
"recipient_id": "user-123",
"subscription": {
"preferences": {}
},
"if": "",
"ref": ""
}
```
# Throttle Node in Automations
Source: https://www.courier.com/docs/platform/automations/throttle
Courier's Throttle Node limits how many messages a user or group receives in a set timeframe. Configure user_id, global, or dynamic scope to reduce volume.
The throttle node limits how many automation-triggered messages a user or group receives within a set timeframe. Events that exceed the configured limit are dropped and do not pass to the next node.
Because Throttle is a part of Automations, it can apply across channels (eg. email, SMS, chat, in-app inbox, push) and providers (eg. Twilio, Sendgrid, email SMTP, Slack, MS Teams, WeChat).
## Throttle Node Example Use Cases
**Alert Notifications**
When sending alert notifications, you may want to limit the number of notifications sent to a user in a given period to avoid bombarding them with too many alerts.
**Email Campaigns**
If you are running an email campaign, you may want to limit the number of emails sent to a customer within a specific timeframe to avoid overwhelming them with too many messages.
In general, a Throttle step can be useful in any situation where you need to control the flow of data or limit the frequency of an action.
Courier will allow you to specify what next steps should be taken when the throttle is triggered, and what steps should be taken when the throttle is not triggered.
## Creating A Throttle Step
To create a Throttle, use the `throttle` action, then fill out details in the step node.
Specify maximum number of events to allow through the throttle, and the time period to throttle by.
You have the options to throttle by:
| Scope | Usage |
| :-------- | :----------------------------------------------------------------------------------------------------------------------------------------- |
| `user_id` | Apply throttling by using `user_id` coming from `data` or `profile` |
| `GLOBAL` | Let Courier decide the throttle parameter. |
| `Dynamic` | You can use arbitrary value available in your `run_context` by referencing it like `refs.data.throttle_key` or `refs.profile.throttle_key` |
Below is an example of a throttle step that allows 1 event to pass through for a given scope in a 7-day period before throttling.
In this example, the throttle is set to be applied dynamically. You have to supply the dynamic key in the following manner to throttle the event. For the `unthrottled` path, you can choose to initiate a different workflow or simply end the automation.
```bash theme={null}
curl --request POST \
--url https://api.courier.com/automations//invoke \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"data": {
"throttle_key": "1234567890"
}
}
```
## Related Resources
Group multiple events into a single notification
Cap message volume by user, topic, or tenant
# Webhook Trigger for Automations
Source: https://www.courier.com/docs/platform/automations/webhook-trigger
Fire Courier Automations from external systems via inbound webhooks. Register a URL, send JSON payloads up to 6 MB, and invoke templates or steps.
Webhook triggers require an Enterprise plan. Contact [Courier Support](mailto:support@courier.com) to enable this feature.
## Register an Inbound Webhook
1. Go to Settings > Inbound Webhooks and click the "Add Inbound Webhook" button.
2. You will be prompted to enter a name and description for the webhook.
After you save the webhook, you will be presented with a unique URL that you can use to send events to Courier. When you set up the webhook trigger in Automations, you will see a list of all of your registered inbound webhooks by the name. The events are tied to the name you select, and you cannot change the name after you save the webhook.
### Configure the Webhook
Use the webhook URL that was generated in the previous step to configure the webhook in your system. Exercise a normal workflow to trigger various events to Courier, which will enable them as configuration options in the Automation trigger.
### Inbound Webhook Payload
Courier accepts any payload up to 6Mb in size.
If the payload is a JSON object, Courier will parse the payload and make the data available to you in the Automation. If the payload is not a JSON object, Courier will make the entire payload available to you in the Automation as a raw string.
If the JSON object is an array, Courier will parse each object in the array and trigger an automation for each incoming event.
For JSON objects, Courier will extract the following fields if they are present.
* `event` - The name of the event that triggered the webhook. You can filter by the name when you set up the webhook trigger in Automations. If no string field named event is present, Courier will use "Custom" as the default name.
* `userId` - A user identifier attached to the event. Courier will use this value to identify the user in the Automation, and automatically load associated profile data into the automation context.
* `properties.courier.automation` - A JSON object containing one of the following fields:
* `template` - The id of an existing automation template to invoke, instead of using a configured webhook trigger.
* `steps` - A JSON object containing the steps for an ad-hoc automation to execute.
If your webhook payload uses different field names for the event name or user ID, contact [Courier Support](mailto:support@courier.com) to configure the field mapping.
## Using the Webhook trigger in an Automation
1. Go to Automations and create a new automation
2. Drag and drop "Webhook" from the list of triggers.
3. In the Source field, select your registered inbound webhook name.
4. After you select the webhook, you can select the event name from a list of any received events, or Custom if your payload did not contain named events.
The data payload in the webhook will be available to you in the Automation in the normal data object, accessed with `refs.data`.
If you don't see any options in the Event field, make sure you have sent at least one event to the webhook from the source system.
## Related Resources
Build workflows visually
Trigger automations from CourierJS, Segment, or Rudderstack
# Brand Designer
Source: https://www.courier.com/docs/platform/content/brands/brand-designer
Customize brand appearance with logos, colors, headers, footers, and custom templates using the Brand Designer.
The Brand Designer lets you customize every aspect of your brand's visual appearance. Configure standard templates with simple settings, or use custom MJML/Handlebars for full control.
## Standard Template
Customize Standard Template Brands with:
* Name
* Logo
* Brand colors (Primary, Secondary, Tertiary)
* Brand Header color
* Brand Footer Social URLs
**Logo Requirements**
* Format: JPEG, PNG, or GIF
* Width: 140px (height is flexible)
* Maximum file size: 5MB
## Custom MJML/Handlebars Template
Use a Custom MJML/Handlebars Template to fully customize the header, footer, and background using HTML, [MJML](https://mjml.io/), or [Handlebars](https://handlebarsjs.com/).
A custom template has four configurable fields:
### Head
The **Head** field injects content into the `` element of the compiled email. Use it for `
```
Courier's MJML compiler generates inline styles that take precedence over class-based rules. Add `!important` to your custom CSS class selectors to ensure they apply. See [CSS Classnames](/docs/platform/content/brands/css-classnames) for details.
### Header
The **Header** field renders MJML or HTML content **above** your template's content blocks. Use it for branded banners, navigation bars, or promotional strips that appear at the top of every email.
```mjml theme={null}
Free shipping on orders over $50
```
### Footer
The **Footer** field renders MJML or HTML content **below** your template's content blocks. Use it for legal disclaimers, unsubscribe links, social media icons, or company address.
```mjml theme={null}
© 2026 Your Company · 123 Main St · Unsubscribe
```
For Outlook-compatible footers, wrap table-based HTML in `` with MSO conditional comments. See [Email Safe Formatting](/docs/platform/content/email-safe-formatting#formatting-mjml-for-outlook-compatibility) for the pattern.
### Background Colors and Layout
Both the **Custom MJML/Handlebars** and **Handlebars** template types expose a **Background Color** field and a **Width** field. The **Handlebars** type also exposes additional layout controls:
| Field | Custom MJML/Handlebars | Handlebars | Description |
| ---------------------------- | :--------------------: | :--------: | ------------------------------------------------------------------- |
| **Background Color** | Yes | Yes | Overall email background color (behind the entire email) |
| **Width** | Yes | Yes | Email container width (default `600px`) |
| **Content Background Color** | No | Yes | Background color for the content area (behind your template blocks) |
| **Footer Background Color** | No | Yes | Background color for the footer section only |
| **Full Width Footer** | No | Yes | Whether the footer background extends to the full email width |
In Custom MJML/Handlebars mode, control content and footer backgrounds directly in your MJML code using `background-color` attributes on `` and `` elements.
These fields can also be set via the [Brands API](/docs/api-reference/brands/create-a-new-brand) on `settings.email.templateOverride`.
***
## Reusing Styles Across Brands
### Inheriting from the Default Brand
Use the **Inherit from Default Brand** toggle to inherit the `Head`, `Header`, or `Footer` from the Default brand. Both brands must use the standard brand type.
### Using Brand Snippets
[Brand Snippets](/docs/platform/content/brands/brand-snippets) let you share custom styles between standard and custom brand templates (Handlebars & MJML).
1. Create a snippet in your default brand with reusable CSS styling. You can use [CSS Classnames](/docs/platform/content/brands/css-classnames) to style Courier blocks.
2. Reference the snippet in a custom brand's `Head` section:
```html theme={null}
```
Snippets in your Default brand can be referenced in custom brands as long as the snippet name is unique.
***
## Previewing Brands
To preview how your notification looks with different brands:
1. Open the template in the designer
2. Open **Preview**
3. Select a brand from **Preview Details > Brand**
***
## Brand Variables Reference
Reference brand attributes in templates using the `var` helper with the `brand` prefix.
### Available Variables
| Variable | Description |
| ------------------------------- | ---------------------- |
| `brand.id` | Brand identifier |
| `brand.colors.primary` | Primary brand color |
| `brand.colors.secondary` | Secondary brand color |
| `brand.colors.tertiary` | Tertiary brand color |
| `brand.email.header.barColor` | Email header bar color |
| `brand.email.header.logo.href` | Logo click-through URL |
| `brand.email.header.logo.image` | Logo image URL |
| `brand.social.facebook` | Facebook profile URL |
| `brand.social.instagram` | Instagram profile URL |
| `brand.social.linkedin` | LinkedIn profile URL |
| `brand.social.medium` | Medium profile URL |
| `brand.social.twitter` | Twitter/X profile URL |
### Examples
**Using brand colors for inline styling:**
```handlebars theme={null}
Welcome to our platform!
```
**Adding social links in a footer:**
```handlebars theme={null}
{{#if (var "brand.social.twitter")}}
Follow us on Twitter
{{/if}}
```
**Referencing the brand logo:**
```handlebars theme={null}
```
# Brand Snippets
Source: https://www.courier.com/docs/platform/content/brands/brand-snippets
Define reusable Handlebars partials—footers, disclaimers, CTAs—at the brand level and reference them in any template. Custom brands inherit snippets.
## What are Brand Snippets?
Snippets are reusable pieces of [Handlebars](https://handlebarsjs.com/) code that you can include in your email templates. Use snippets for content that appears across multiple templates, like:
* Standard legal disclaimers or footer text
* Social media links
* Promotional banners or CTAs
* Support contact information
Snippets are defined at the brand level and can be referenced by name in any template that uses that brand.
## Creating a Brand Snippet
To create a Snippet:
1. Navigate to the **Brands** tab in Courier
2. Select the brand you want to add the snippet to
3. Click the **Snippets** tab
4. Click **Add Snippet**
5. Give your snippet a name (this is how you'll reference it in templates)
6. Add your Handlebars content
## Using Brand Snippets in Templates
Reference snippets in a **Template Block** using Handlebars partial syntax:
```handlebars theme={null}
{{>snippet_name}}
```
For example, if you created a snippet named `legal_footer`:
```handlebars theme={null}
{{>legal_footer}}
```
Using the notification preview allows you to see the snippet rendered:
## Customizing Snippets with Variables
Snippets can include variables that get populated from data passed in the Send API call. Use standard Handlebars variable syntax within your snippet:
```handlebars theme={null}
Thanks for being a customer since {{profile.member_since}}!
Your current plan: {{data.plan_name}}
```
Variables in snippets have access to the same data context as the rest of your template, including `profile`, `data`, and `brand` variables.
## Snippet Inheritance from the Default Brand
Custom brands **extend** the default brand's snippets. This means:
* Snippets defined in the default brand are automatically available in all custom brands
* If a custom brand defines a snippet with the same name, it overrides the default brand's snippet
* Custom brands can have their own additional snippets alongside inherited ones
**Example**:
* Default brand has snippets: `footer`, `social_links`, `legal_text`
* Custom brand "Acme" has snippet: `footer` (custom version)
* When using Acme brand: `footer` uses Acme's version, `social_links` and `legal_text` use the default brand's versions
This inheritance model lets you:
* Define common snippets once in the default brand
* Override specific snippets per brand when needed
* Avoid duplicating content across brands
## Related Resources
Learn how to configure and use brands
Formatting and logic helpers for snippets
# Notification Brands Overview
Source: https://www.courier.com/docs/platform/content/brands/brands-overview
Courier Brands apply a shared logo, colors, headers, and footers to templates. Use them for white-labeling, multi-product styling, or A/B testing.
## Overview
Brands in Courier define the visual appearance of your notifications. A brand includes your logo, colors, headers, footers, and custom styling that automatically apply to templates using that brand. Brands ensure visual consistency across all notifications without manually styling each template.
## When to Use Brands
* **Multi-tenant SaaS (White-labeling)**: Send notifications on behalf of your customers with their branding. Each customer gets their own brand with logo, colors, and styling.
* **Multiple Product Lines**: Maintain distinct visual identities for different products or services within your organization.
* **Regional or Market Variations**: Customize branding for different geographic regions or market segments while keeping template logic consistent.
* **A/B Testing Brand Styles**: Test different visual treatments by creating brand variants and comparing delivery metrics.
***
## Getting Started
### The Default Brand
Every workspace has a default brand that cannot be deleted. Customizing it is important because:
* Every email notification uses the Default Brand unless you manually disable Brands in template settings
* If you enable brands on a notification, the Default Brand is the fallback when no brand is specified in the Send API call
The default brand can be customized and renamed like any other brand.
### Creating a New Brand
1. In the sidebar, open the **Content** dropdown and select **Brands**
2. Click the **+ New** button to create a new brand
3. Enter a **name** for your brand (e.g., "Acme Client", "Northern EMEA")
4. Optionally set a `brand_id` for easier API reference
### Setting a Custom Brand as Default
To set a custom brand as your default, open the brand settings and click **Set As Default**.
***
## Sending Branded Notifications
To send a branded email:
1. Enable **Brand** in the template's settings
2. *(Optional)* Include a Brand ID in the [Send API call](/docs/api-reference/send/send-a-message). If omitted, Courier uses the Default Brand.
***
## Brands API
Brands are fully API-enabled. Create, update, and delete brands programmatically using the [Brands API](/docs/api-reference/brands/list-brands).
***
## Next Steps
Customize logos, colors, headers, footers, and templates
Create reusable Handlebars components across brands
Style Courier blocks with custom CSS
Send emails from your own domain
# Email Template CSS Classes
Source: https://www.courier.com/docs/platform/content/brands/css-classnames
Style Courier email templates using CSS class names for content blocks, text, and layout. All rules require !important because MJML outputs inline styles.
Courier exposes standardized CSS classes on every email template element. Use these to customize the look and feel of your branded email notifications.
## Adding CSS to Your Brand Templates
Courier email templates use inline styles by default. You can add custom CSS in the `