Skip to main content
In this tutorial, you’ll build a TypeScript function that lets an AI agent send notifications across multiple channels with a single API call. The agent decides what to communicate and how urgent it is. Courier handles channel selection, failover, and delivery.

Prerequisites

Before you start, make sure you have:

Which channels work out of the box?

Not every channel requires provider setup. Here’s what you can use right away and what needs configuration:
ChannelReady out of the box?Setup needed
InboxYesNone. Courier’s in-app inbox works immediately.
EmailYes (test mode)Courier provides a built-in email provider for testing. For production, add a provider like SendGrid or AWS SES.
PushNoRequires a push provider (Firebase, APNs) and device tokens on user profiles.
SMSNoRequires an SMS provider (Twilio, Vonage) and phone numbers on user profiles.
Channels without a configured provider show as Unroutable in the logs. Courier skips them automatically and tries the next channel in the list. Start with "low" urgency (inbox only) and add channels as you set up providers.

Project setup

1

Initialize the project

Create a new directory and install the Courier SDK:
mkdir agent-courier-notifications && cd agent-courier-notifications
npm init -y
npm install @trycourier/courier dotenv
npm install typescript tsx --save-dev
npx tsc --init
2

Configure your API key

Create a .env file in your project root:
COURIER_API_KEY=your_courier_api_key
3

Create the project structure

agent-courier-notifications/
├── src/
│   └── send-notification.ts
├── .env
├── package.json
└── tsconfig.json

Understanding the routing model

Every Courier send call accepts a routing object with two fields:
routing: {
  method: "all" | "single",
  channels: ["email", "sms", "push", "inbox"]
}
MethodBehavior
singleTry each channel in order, stop at the first successful delivery
allDeliver to every channel in the array simultaneously
The channel array is your priority list. For single, Courier works through it top to bottom. For all, every channel fires at once.
For a full walkthrough of routing configuration (including the visual designer), see How to configure multi-channel routing.

Pattern 1: Urgency-based routing

The most common pattern for agent-driven notifications. Instead of hardcoding a channel, the agent expresses urgency and a helper function maps it to a routing strategy. Create src/send-notification.ts:
import Courier from "@trycourier/courier";
import "dotenv/config";

const courier = new Courier();

function routingForUrgency(urgency: string) {
  const strategies: Record<string, { method: "all" | "single"; channels: string[] }> = {
    critical: { method: "all", channels: ["email", "sms", "push", "inbox"] },
    high:     { method: "single", channels: ["push", "email", "inbox"] },
    normal:   { method: "single", channels: ["email", "inbox"] },
    low:      { method: "single", channels: ["inbox"] },
  };
  return strategies[urgency] ?? strategies.normal;
}

export async function sendNotification(
  userId: string,
  title: string,
  body: string,
  urgency: string
) {
  const routing = routingForUrgency(urgency);

  const { requestId } = await courier.send.message({
    message: {
      to: { user_id: userId },
      content: { title, body },
      routing,
    },
  });

  return { requestId, channels: routing.channels, method: routing.method };
}
Here’s what each urgency level does:
UrgencyMethodChannelsWhen to use
criticalallemail, SMS, push, inboxSecurity alerts, outages, account compromises
highsinglepush → email → inboxTime-sensitive operational alerts
normalsingleemail → inboxReports, digests, routine updates
lowsingleinboxInformational, non-time-sensitive

Test it

Add a quick test script at the bottom of send-notification.ts (or in a separate file):
async function main() {
  const urgency = process.argv[2] || "low";
  const result = await sendNotification(
    "test_user_123",
    "Weekly report ready",
    "Your weekly usage report is now available.",
    urgency
  );
  console.log("Sent:", result);
}

main().catch(console.error);
Start with low urgency to confirm your setup works. This sends to inbox only, which requires no provider configuration:
npx tsx src/send-notification.ts low
Check Courier Logs to confirm the notification was delivered to inbox. Then try higher urgency levels as you configure additional channels:
npx tsx src/send-notification.ts normal    # email + inbox
npx tsx src/send-notification.ts high      # push → email → inbox
npx tsx src/send-notification.ts critical  # all channels at once
If a channel shows as Unroutable in the logs, that just means you haven’t configured a provider for it yet. With method: "single", Courier automatically skips to the next channel in the list, so your notification still gets delivered through whichever channels are set up.

Pattern 2: Channel failover

Some users don’t have push tokens configured. Some haven’t verified their phone number. With method: "single", Courier attempts each channel in order and stops at the first success:
const { requestId } = await courier.send.message({
  message: {
    to: { user_id: userId },
    content: { title, body },
    routing: {
      method: "single",
      channels: ["push", "email", "sms", "inbox"],
    },
  },
});
If push fails (no device token), Courier tries email. If email fails (bad address), it tries SMS. If SMS fails, it falls back to inbox. The agent doesn’t need to check user profiles before sending.
The requestId returned by the send call lets you check delivery status later using the Messages API. This is useful for agents that need to confirm a notification landed before continuing.

Pattern 3: Provider failover

Channel failover handles “which channel.” Provider failover handles “which service within a channel.” If your primary email provider goes down, Courier can silently retry through a backup. Add a channels object to configure per-channel provider ordering:
const { requestId } = await courier.send.message({
  message: {
    to: { user_id: userId },
    content: { title, body },
    routing: {
      method: "single",
      channels: ["email", "push", "inbox"],
    },
    channels: {
      email: {
        providers: ["sendgrid", "ses"],
        routing_method: "single",
      },
    },
  },
});
This gives you two layers of failover:
  1. Channel level: email → push → inbox
  2. Provider level (within email): SendGrid → AWS SES
If SendGrid returns a 5xx, Courier retries through SES automatically.
You can configure provider failover for any channel, not just email. For example, set up Twilio as a primary SMS provider with Vonage as a backup.

Pattern 4: Template-based multi-channel

For notifications that need branded designs, channel-specific copy, or complex layouts, use a Courier template instead of inline content. Design the template once in the template designer, and Courier renders the appropriate version for each channel.
const { requestId } = await courier.send.message({
  message: {
    to: { user_id: userId },
    template: "DEPLOYMENT_ALERT",
    data: {
      service: "api-server",
      environment: "production",
      exitCode: 1,
      timestamp: new Date().toISOString(),
    },
    routing: {
      method: "all",
      channels: ["email", "push", "inbox"],
    },
  },
});
The data object passes dynamic values into your template variables. The email version can include a full HTML layout with error details, the push notification can be a short summary, and the inbox message can include action buttons.
To create the DEPLOYMENT_ALERT template, go to Templates, click New, and add channel blocks for each delivery channel. Use {service}, {environment}, and {exitCode} as template variables.

Exposing to your agent

The sendNotification function from Pattern 1 works as a tool in any agent framework. Here are three ways to connect it:

Option 1: Courier MCP server

If your agent runs in Cursor, Claude Code, or another MCP-compatible environment, the Courier MCP server exposes a send_message tool that already supports routing, channels, and template fields. No custom code needed. Add to your .cursor/mcp.json:
{
  "mcpServers": {
    "courier": {
      "url": "https://mcp.courier.com",
      "headers": {
        "api_key": "your_courier_api_key"
      }
    }
  }
}

Option 2: Courier CLI

For agents that work through shell commands, use courier send message with the same JSON structure:
courier send message --message '{
  "to": {"user_id": "test_user_123"},
  "content": {"title": "Deployment failed", "body": "Exit code 1"},
  "routing": {"method": "all", "channels": ["email", "push", "inbox"]}
}'
See the CLI reference for installation and full command documentation.

Option 3: Custom tool definition

Wrap sendNotification as a tool in your framework of choice. The function signature maps directly to a tool schema: userId, title, body, and urgency as parameters, with the urgency descriptions guiding the LLM’s selection. This works with OpenAI function calling, Vercel AI SDK, LangChain, or any framework that supports structured tool definitions.

Verify delivery

After sending, check delivery status in two ways:
  1. Courier Logs UI: Go to app.courier.com/logs to see the full delivery timeline for each notification, including which channels were attempted, which succeeded, and which providers were used.
  2. Messages API: Query delivery status programmatically:
const message = await courier.messages.get("your_request_id");
console.log(message.status); // DELIVERED, SENT, UNDELIVERABLE, etc.
In the logs, you’ll see one entry per channel attempted. Common statuses:
StatusMeaning
DeliveredThe provider accepted and delivered the notification
SentThe provider accepted the notification (delivery confirmation pending)
UnroutableNo provider is configured for this channel. Expected if you haven’t set up that channel yet.
UndeliverableA provider is configured but delivery failed (bad address, expired token, etc.)

What’s next

Configure multi-channel routing

Set up routing rules in the Send API and the visual designer

Agent quickstart

Set up your AI agent with Courier’s MCP server, CLI, and Skills

CLI reference

Send and manage notifications from the command line

Send API reference

Full API documentation for the Send endpoint