Skip to main content
Courier Elemental lets you define notification content as JSON instead of using the visual template designer. This is useful when your content is generated dynamically, managed in code, or needs to differ per channel. This tutorial walks through building a real notification end-to-end: starting with a simple message, then layering in channel-specific content, conditional rendering, and dynamic lists.

Prerequisites

Send a Simple Elemental Message

The fastest way to use Elemental is the sugar syntax: just title and body. Courier automatically converts this into the full Elemental format behind the scenes.
1

Send with title and body

curl -X POST https://api.courier.com/send \
  -H "Authorization: Bearer $COURIER_AUTH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "message": {
      "to": { "email": "jane@example.com" },
      "content": {
        "title": "Your order has shipped",
        "body": "Hi {{name}}, your order #{{order_id}} is on its way. Expected delivery: {{delivery_date}}."
      },
      "data": {
        "name": "Jane",
        "order_id": "ORD-9042",
        "delivery_date": "Feb 12, 2026"
      }
    }
  }'
This sends an email with the subject “Your order has shipped” and the body text populated with your template variables. No stored template required.
2

Verify delivery

Check the Message Logs in the Courier dashboard to confirm the message was delivered and see the rendered output.
The sugar syntax is ideal for simple messages. For multi-element layouts, channel-specific content, or dynamic logic, use the full Elemental format below.

Use Full Elemental Format

Full Elemental gives you complete control over the notification structure. Every template requires a version field ("2022-01-01") and an elements array.
1

Build a structured notification

This example creates an order confirmation with a heading, body text, and a call-to-action button.
curl -X POST https://api.courier.com/send \
  -H "Authorization: Bearer $COURIER_AUTH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "message": {
      "to": { "email": "jane@example.com" },
      "content": {
        "version": "2022-01-01",
        "elements": [
          {
            "type": "meta",
            "title": "Order #{{order_id}} Confirmed"
          },
          {
            "type": "text",
            "content": "Hi {{name}}, your order has been confirmed and is being prepared."
          },
          {
            "type": "action",
            "content": "Track Your Order",
            "href": "https://example.com/orders/{{order_id}}"
          },
          {
            "type": "divider"
          },
          {
            "type": "text",
            "content": "Questions? Reply to this email or visit our help center.",
            "text_style": "subtext"
          }
        ]
      },
      "data": {
        "name": "Jane",
        "order_id": "ORD-9042"
      }
    }
  }'

Element Types at a Glance

ElementPurposeKey properties
metaSets the email subject / push titletitle
textBody text with optional formattingcontent, text_style, format, align
actionButton or linkcontent, href, style (button or link)
imageInline imagesrc, alt_text, width, href
dividerHorizontal rulecolor
quoteBlockquote textcontent, border_color
htmlRaw HTML (email only)content
groupContainer for conditional/loop logicelements, if, loop
channelChannel-specific content branchchannel, elements, raw
columnsMulti-column layoutelements (array of column nodes)
For full property details, see the Elements Reference.

Customize Content Per Channel

Use channel elements to send different content to different channels from a single API call. This is one of Elemental’s most powerful features.
{
  "version": "2022-01-01",
  "elements": [
    {
      "type": "channel",
      "channel": "email",
      "elements": [
        { "type": "meta", "title": "Order #{{order_id}} Confirmed" },
        { "type": "text", "content": "Hi {{name}}, here are your order details..." },
        { "type": "image", "src": "{{product_image}}", "alt_text": "{{product_name}}" },
        { "type": "action", "content": "Track Order", "href": "{{tracking_url}}" }
      ]
    },
    {
      "type": "channel",
      "channel": "sms",
      "elements": [
        { "type": "text", "content": "Order #{{order_id}} confirmed! Track: {{tracking_url}}" }
      ]
    },
    {
      "type": "channel",
      "channel": "push",
      "elements": [
        { "type": "meta", "title": "Order Confirmed" },
        { "type": "text", "content": "Your order #{{order_id}} is on its way." }
      ]
    }
  ]
}
You can also use the channels property on individual elements to show or hide them by channel without wrapping in a channel block:
{
  "type": "text",
  "content": "This only appears in email and push",
  "channels": ["email", "push"]
}

Add Conditional Logic

Use the if property on any element to conditionally render it based on your data.
{
  "version": "2022-01-01",
  "elements": [
    {
      "type": "text",
      "content": "Welcome back, {{name}}!"
    },
    {
      "type": "text",
      "content": "As a premium member, you have early access to new features.",
      "if": "data.tier === 'premium'"
    },
    {
      "type": "action",
      "content": "Upgrade to Premium",
      "href": "https://example.com/upgrade",
      "if": "data.tier !== 'premium'"
    }
  ]
}
The if expression is evaluated as JavaScript against the data object you pass in the send call.

Render Dynamic Lists

The loop property iterates over an array in your data and renders the element once per item.
{
  "version": "2022-01-01",
  "elements": [
    {
      "type": "text",
      "content": "Your recent orders:",
      "text_style": "h2"
    },
    {
      "type": "group",
      "loop": "data.orders",
      "elements": [
        {
          "type": "text",
          "content": "**{{$.item.name}}** ; {{$.item.quantity}}x ; ${{$.item.price}}"
        }
      ]
    },
    {
      "type": "divider"
    },
    {
      "type": "text",
      "content": "Total: ${{total}}"
    }
  ]
}
Inside a loop, {{$.item}} refers to the current item and {{$.index}} gives the zero-based index.
The format property on text elements supports "markdown" for bold, italic, and link rendering. Use double asterisks (**bold**) or single asterisks (*italic*) in your content strings.

Send to a Stored User Profile

Instead of specifying an email address directly, you can send to a user by their profile ID. Courier looks up their contact details from the stored profile.
curl -X POST https://api.courier.com/send \
  -H "Authorization: Bearer $COURIER_AUTH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "message": {
      "to": { "user_id": "user_123" },
      "content": {
        "version": "2022-01-01",
        "elements": [
          { "type": "meta", "title": "Weekly Summary" },
          { "type": "text", "content": "Here is your weekly activity summary, {{name}}." }
        ]
      },
      "data": { "name": "Jane" }
    }
  }'

What’s Next