Skip to main content
Build a notification template from code, route it, publish it, and send it. The example is a small shipping-update email, and you’ll finish with a live template plus a reusable routing strategy you can attach to others. Every step is shown in curl, Node.js, and Python.

How templates and routing strategies work together

Two pieces combine to send a notification:
  • A template holds the content: the subject, text, buttons, and variables, written in Courier’s Elemental format. It’s what the notification says.
  • A routing strategy holds the delivery rules: which channels to use (email, SMS, inbox) and in what order. It’s how the notification gets there.
A template points at a routing strategy by id, and one strategy can back many templates. Define “email, then SMS as a fallback” once, reference it from every order notification, and they all deliver the same way. Update the strategy, and all of them change at once. A template without routing can’t deliver, so you’ll create the strategy first, then reuse it.

Prerequisites

  • A Courier API key. The SDKs read it from COURIER_API_KEY; curl passes it as a bearer token.
  • A provider for the channel you’ll send on. The test environment ships with a built-in email provider, so you can send right away. To use your own or send on other channels, add an integration.

Step 1: Create a routing strategy

Create the reusable delivery rules first. This basic strategy sends over email. method controls delivery: single tries channels in order until one succeeds (a fallback chain), and all sends to every channel at once. Add more channels to channels (for example ["email", "inbox"]) to fan out. Courier maps each channel to the provider you configured in your workspace.
curl -X POST https://api.courier.com/routing-strategies \
  -H "Authorization: Bearer $COURIER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Order notifications",
    "routing": { "method": "single", "channels": ["email"] }
  }'
const strategy = await courier.routingStrategies.create({
  name: "Order notifications",
  routing: { method: "single", channels: ["email"] },
});

console.log(strategy.id); // rs_...
strategy = courier.routing_strategies.create(
    name="Order notifications",
    routing={"method": "single", "channels": ["email"]},
)

print(strategy.id)  # rs_...
Keep the returned id (rs_...). You’ll attach it to every template that should route this way.

Step 2: Create the template

Pass a notification object with a name, the routing strategy from Step 1, and content in the Elemental shape (a version plus elements like meta, text, and action). Courier creates it as a draft and returns the template, including the id you’ll publish and send.
curl -X POST https://api.courier.com/notifications \
  -H "Authorization: Bearer $COURIER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "notification": {
      "name": "Shipping update",
      "routing": { "strategy_id": "rs_01abc123" },
      "content": {
        "version": "2022-01-01",
        "elements": [
          { "type": "meta", "title": "Your order {{order_id}} has shipped" },
          { "type": "text", "content": "Hi {{name}}, your package is on the way. Tracking: {{tracking_url}}." },
          { "type": "action", "content": "Track shipment", "href": "{{tracking_url}}" }
        ]
      }
    }
  }'
const template = await courier.notifications.create({
  notification: {
    name: "Shipping update",
    routing: { strategy_id: strategy.id },
    content: {
      version: "2022-01-01",
      elements: [
        { type: "meta", title: "Your order {{order_id}} has shipped" },
        {
          type: "text",
          content:
            "Hi {{name}}, your package is on the way. Tracking: {{tracking_url}}.",
        },
        { type: "action", content: "Track shipment", href: "{{tracking_url}}" },
      ],
    },
  },
});

console.log(template.id); // nt_...
template = courier.notifications.create(
    notification={
        "name": "Shipping update",
        "routing": {"strategy_id": strategy.id},
        "content": {
            "version": "2022-01-01",
            "elements": [
                {"type": "meta", "title": "Your order {{order_id}} has shipped"},
                {
                    "type": "text",
                    "content": "Hi {{name}}, your package is on the way. Tracking: {{tracking_url}}.",
                },
                {"type": "action", "content": "Track shipment", "href": "{{tracking_url}}"},
            ],
        },
    },
)

print(template.id)  # nt_...
The response includes id and state (DRAFT), plus the content and routing you attached.

Step 3: Publish it

A draft isn’t live yet. Publish it so it can send.
curl -X POST https://api.courier.com/notifications/nt_01abc123/publish \
  -H "Authorization: Bearer $COURIER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
await courier.notifications.publish(template.id);
courier.notifications.publish(template.id)
Publishing is idempotent: republishing a live template is safe to retry.

Step 4: Send a notification

Send the template by id with the recipient and values for the variables in the content (order_id, name, tracking_url). The template’s routing decides the channels, so the send stays short. The example uses email, but you can use { "user_id": "..." } instead.
Use a Test API key while you iterate.
curl -X POST https://api.courier.com/send \
  -H "Authorization: Bearer $COURIER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": {
      "template": "nt_01abc123",
      "to": { "email": "jane@example.com" },
      "data": {
        "order_id": "ORD-1001",
        "name": "Jane",
        "tracking_url": "https://example.com/track/ORD-1001"
      }
    }
  }'
const { requestId } = await courier.send.message({
  message: {
    template: template.id,
    to: { email: "jane@example.com" },
    data: {
      order_id: "ORD-1001",
      name: "Jane",
      tracking_url: "https://example.com/track/ORD-1001",
    },
  },
});

console.log(requestId);
response = courier.send.message(
    message={
        "template": template.id,
        "to": {"email": "jane@example.com"},
        "data": {
            "order_id": "ORD-1001",
            "name": "Jane",
            "tracking_url": "https://example.com/track/ORD-1001",
        },
    },
)

print(response.request_id)
The send returns a request id. Check message logs for delivery status.

Step 5: Reuse the strategy on another template

This is the payoff. A second template, say an order-delivered email, reuses the same strategy id. No new routing to define: it inherits the same channels, and if you later change the strategy, both templates follow.
curl -X POST https://api.courier.com/notifications \
  -H "Authorization: Bearer $COURIER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "notification": {
      "name": "Order delivered",
      "routing": { "strategy_id": "rs_01abc123" },
      "content": {
        "version": "2022-01-01",
        "elements": [
          { "type": "meta", "title": "Your order {{order_id}} was delivered" },
          { "type": "text", "content": "Hi {{name}}, your package just arrived. Thanks for shopping with us!" }
        ]
      }
    }
  }'
const delivered = await courier.notifications.create({
  notification: {
    name: "Order delivered",
    routing: { strategy_id: strategy.id },
    content: {
      version: "2022-01-01",
      elements: [
        { type: "meta", title: "Your order {{order_id}} was delivered" },
        {
          type: "text",
          content:
            "Hi {{name}}, your package just arrived. Thanks for shopping with us!",
        },
      ],
    },
  },
});
delivered = courier.notifications.create(
    notification={
        "name": "Order delivered",
        "routing": {"strategy_id": strategy.id},
        "content": {
            "version": "2022-01-01",
            "elements": [
                {"type": "meta", "title": "Your order {{order_id}} was delivered"},
                {
                    "type": "text",
                    "content": "Hi {{name}}, your package just arrived. Thanks for shopping with us!",
                },
            ],
        },
    },
)
Both templates now share one strategy. Switch it from email to “email, then SMS” once, and every template that references it picks up the change.

Troubleshooting

Your API key is missing or invalid. Confirm COURIER_API_KEY is set in the environment running the call and matches the workspace you’re targeting (test vs. production).
Courier accepted the send but couldn’t pick a channel or provider. Either the template has no routing strategy attached (Steps 1 and 2), or the strategy’s channel has no provider configured in your workspace.

What’s next

Manage templates via the API

Update, replace, list, and archive templates

Routing strategies

Channel order, fallbacks, and provider config

How to build notifications with Elemental

Patterns for content and channel overrides

Send API

Request body, profiles, and routing