Skip to main content
The Courier Create API lets you create and update stored notification templates over HTTP. It powers the same Elemental model you edit in Design Studio and embed with the Courier Create React package. Use it from a script or CI when you want versioned, repeatable changes (for example, promoting a template across environments). This tutorial walks through a realistic order confirmation email: save a draft, refine the content, publish, then fetch the published version. You should be comfortable with Elemental basics. For request fields and status codes, see the Courier Create API reference.
These endpoints manage templates scoped to a specific tenant (/tenants/{tenant_id}/templates/...). They are part of the Courier Create feature set and are separate from workspace-level notification templates you configure in the Courier dashboard.

Prerequisites

  • Courier API key with permission to manage tenant notification templates (or a JWT with the right scopes if you mirror Courier Create authentication)
  • A tenant ID (replace placeholders below)
  • Node.js (npm install @trycourier/courier) or Python (pip install trycourier)
Pick a stable TEMPLATE_ID per tenant; reusing the same ID updates the template in place. Export:
export COURIER_API_KEY="your_api_key"
export TENANT_ID="your-tenant-id"
export TEMPLATE_ID="order-confirmation-api-tutorial"

Step 1: Create a draft template

Save an initial draft with published: false. The body uses TenantTemplateInput with required content (Elemental). This example uses email-oriented elements at the root: meta (subject), text, and an action link.
curl -X PUT "https://api.courier.com/tenants/$TENANT_ID/templates/$TEMPLATE_ID" \
  -H "Authorization: Bearer $COURIER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "published": false,
    "template": {
      "content": {
        "version": "2022-01-01",
        "elements": [
          { "type": "meta", "title": "Order {{order_id}} confirmed" },
          {
            "type": "text",
            "content": "Hi {{name}}, we received your order and are preparing it."
          },
          {
            "type": "action",
            "content": "View order",
            "href": "https://example.com/orders/{{order_id}}"
          }
        ]
      }
    }
  }'
Expect 201 if the template ID is new for that tenant, or 200 if you run the tutorial again.

Step 2: Update the draft

Refine copy without publishing yet: send another PUT with the same template_id, still published: false. Add a divider and a support line so the email matches what you might ship after review.
In a real codebase, store the template.content object in one JSON file or shared module and merge edits there so you are not copying large payloads between steps.
curl -X PUT "https://api.courier.com/tenants/$TENANT_ID/templates/$TEMPLATE_ID" \
  -H "Authorization: Bearer $COURIER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "published": false,
    "template": {
      "content": {
        "version": "2022-01-01",
        "elements": [
          { "type": "meta", "title": "Order {{order_id}} confirmed" },
          {
            "type": "text",
            "content": "Hi {{name}}, we received your order and are preparing it."
          },
          {
            "type": "action",
            "content": "View order",
            "href": "https://example.com/orders/{{order_id}}"
          },
          { "type": "divider" },
          {
            "type": "text",
            "content": "Need help? Reply to this email or visit our help center.",
            "text_style": "subtext"
          }
        ]
      }
    }
  }'

Step 3: Publish

Call POST /tenants/{tenant_id}/templates/{template_id}/publish with an empty body (or {"version": "latest"}) to publish the latest draft.
curl -X POST "https://api.courier.com/tenants/$TENANT_ID/templates/$TEMPLATE_ID/publish" \
  -H "Authorization: Bearer $COURIER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
You can skip a separate publish call by setting published: true on a single PUT when you are ready to go live.

Step 4: Read the published version

Confirm what recipients will see by fetching the published snapshot:
curl -s "https://api.courier.com/tenants/$TENANT_ID/templates/$TEMPLATE_ID/versions/published" \
  -H "Authorization: Bearer $COURIER_API_KEY" | jq .
Use latest for the newest saved draft (published or not, depending on your workflow) and published for what is live.

Step 5: Send a message using the template

Reference a tenant template in the Send API by prefixing the template ID with tenant/ and setting context.tenant_id:
curl -X POST "https://api.courier.com/send" \
  -H "Authorization: Bearer $COURIER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": {
      "to": { "email": "recipient@example.com" },
      "template": "tenant/'"$TEMPLATE_ID"'",
      "context": { "tenant_id": "'"$TENANT_ID"'" },
      "data": {
        "order_id": "ORD-12345",
        "name": "Jane"
      }
    }
  }'
The tenant/ prefix tells the Send API to resolve the template from the tenant’s notification map rather than from workspace-level notifications.

What you built

You now have a published template with version history: drafts from PUT with published: false, a live version from POST .../publish, a way to audit what is live via GET .../versions/published, and a send call that delivers messages using the template.

What’s Next

Courier Create API reference

Endpoints, fields, and error codes

Elemental tutorial

More patterns for content structure

Preview in Studio

Test with sample data before you publish

Send API

Deliver messages that reference this template