> ## Documentation Index
> Fetch the complete documentation index at: https://www.courier.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# How to Use the Courier Create API

> Create a draft notification template for a tenant, iterate with Elemental content, publish it, and read back the published version using the Courier Create template endpoints.

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](/platform/content/design-studio/design-studio-overview) and embed with the [Courier Create](/platform/create/installation) 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](/platform/content/elemental/elemental-overview) basics. For request fields and status codes, see the [Courier Create API](/platform/create/courier-create-api) reference.

<Info>
  These endpoints manage templates scoped to a specific [tenant](/platform/tenants/tenants-overview) (`/tenants/{tenant_id}/templates/...`). They are part of the [Courier Create](/platform/create/installation) feature set and are separate from workspace-level notification templates you configure in the Courier dashboard.
</Info>

## Prerequisites

* Courier [API key](/platform/workspaces/environments-api-keys) with permission to manage tenant notification templates (or a JWT with the right scopes if you mirror [Courier Create authentication](/platform/create/authentication))
* A [tenant ID](/platform/tenants/tenants-overview) (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:

```bash theme={null}
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.

<CodeGroup>
  ```bash cURL icon="terminal" wrap theme={null}
  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}}"
            }
          ]
        }
      }
    }'
  ```

  ```javascript Node.js icon="node-js" theme={null}
  import Courier from "@trycourier/courier";

  const client = new Courier();

  const draft = await client.tenants.templates.replace(process.env.TEMPLATE_ID, {
    tenant_id: process.env.TENANT_ID,
    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}}",
          },
        ],
      },
    },
  });

  console.log("Saved draft version:", draft.version);
  ```

  ```python Python icon="python" theme={null}
  import os
  from courier import Courier

  client = Courier()

  draft = client.tenants.templates.replace(
      template_id=os.environ["TEMPLATE_ID"],
      tenant_id=os.environ["TENANT_ID"],
      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}}",
                  },
              ],
          }
      },
  )

  print("Saved draft version:", draft.version)
  ```
</CodeGroup>

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.

<Tip>
  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.
</Tip>

<CodeGroup>
  ```bash cURL icon="terminal" wrap theme={null}
  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"
            }
          ]
        }
      }
    }'
  ```

  ```javascript Node.js icon="node-js" theme={null}
  import Courier from "@trycourier/courier";

  const client = new Courier();

  await client.tenants.templates.replace(process.env.TEMPLATE_ID, {
    tenant_id: process.env.TENANT_ID,
    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",
          },
        ],
      },
    },
  });
  ```

  ```python Python icon="python" theme={null}
  import os
  from courier import Courier

  client = Courier()

  client.tenants.templates.replace(
      template_id=os.environ["TEMPLATE_ID"],
      tenant_id=os.environ["TENANT_ID"],
      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",
                  },
              ],
          }
      },
  )
  ```
</CodeGroup>

## Step 3: Publish

Call `POST /tenants/{tenant_id}/templates/{template_id}/publish` with an empty body (or `{"version": "latest"}`) to publish the latest draft.

<CodeGroup>
  ```bash cURL icon="terminal" wrap theme={null}
  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 '{}'
  ```

  ```javascript Node.js icon="node-js" theme={null}
  import Courier from "@trycourier/courier";

  const client = new Courier();

  const published = await client.tenants.templates.publish(process.env.TEMPLATE_ID, {
    tenant_id: process.env.TENANT_ID,
  });

  console.log("Published version:", published.version, published.published_at);
  ```

  ```python Python icon="python" theme={null}
  import os
  from courier import Courier

  client = Courier()

  published = client.tenants.templates.publish(
      template_id=os.environ["TEMPLATE_ID"],
      tenant_id=os.environ["TENANT_ID"],
  )

  print("Published version:", published.version, published.published_at)
  ```
</CodeGroup>

<Tip>
  You can skip a separate publish call by setting `published: true` on a single `PUT` when you are ready to go live.
</Tip>

## Step 4: Read the published version

Confirm what recipients will see by fetching the published snapshot:

<CodeGroup>
  ```bash cURL icon="terminal" wrap theme={null}
  curl -s "https://api.courier.com/tenants/$TENANT_ID/templates/$TEMPLATE_ID/versions/published" \
    -H "Authorization: Bearer $COURIER_API_KEY" | jq .
  ```

  ```javascript Node.js icon="node-js" theme={null}
  import Courier from "@trycourier/courier";

  const client = new Courier();

  const snapshot = await client.tenants.templates.versions.retrieve("published", {
    tenant_id: process.env.TENANT_ID,
    template_id: process.env.TEMPLATE_ID,
  });

  console.log(JSON.stringify(snapshot.data, null, 2));
  ```

  ```python Python icon="python" theme={null}
  import os
  from courier import Courier

  client = Courier()

  snapshot = client.tenants.templates.versions.retrieve(
      "published",
      tenant_id=os.environ["TENANT_ID"],
      template_id=os.environ["TEMPLATE_ID"],
  )

  print(snapshot.data)
  ```
</CodeGroup>

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](/platform/sending/send-message) by prefixing the template ID with `tenant/` and setting `context.tenant_id`:

<CodeGroup>
  ```bash cURL icon="terminal" wrap 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": "recipient@example.com" },
        "template": "tenant/'"$TEMPLATE_ID"'",
        "context": { "tenant_id": "'"$TENANT_ID"'" },
        "data": {
          "order_id": "ORD-12345",
          "name": "Jane"
        }
      }
    }'
  ```

  ```javascript Node.js icon="node-js" theme={null}
  import Courier from "@trycourier/courier";

  const client = new Courier();

  const { requestId } = await client.send({
    message: {
      to: { email: "recipient@example.com" },
      template: `tenant/${process.env.TEMPLATE_ID}`,
      context: { tenant_id: process.env.TENANT_ID },
      data: {
        order_id: "ORD-12345",
        name: "Jane",
      },
    },
  });

  console.log("Sent:", requestId);
  ```

  ```python Python icon="python" theme={null}
  import os
  from courier import Courier

  client = Courier()

  response = client.send(
      message={
          "to": {"email": "recipient@example.com"},
          "template": f"tenant/{os.environ['TEMPLATE_ID']}",
          "context": {"tenant_id": os.environ["TENANT_ID"]},
          "data": {
              "order_id": "ORD-12345",
              "name": "Jane",
          },
      },
  )

  print("Sent:", response.request_id)
  ```
</CodeGroup>

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

<CardGroup cols={2}>
  <Card title="Courier Create API reference" icon="book" href="/platform/create/courier-create-api">
    Endpoints, fields, and error codes
  </Card>

  <Card title="Elemental tutorial" icon="layer-group" href="/tutorials/content/how-to-use-elemental">
    More patterns for content structure
  </Card>

  <Card title="Preview in Studio" icon="eye" href="/platform/content/template-designer/how-to-preview-notification">
    Test with sample data before you publish
  </Card>

  <Card title="Send API" icon="paper-plane" href="/platform/sending/send-message">
    Deliver messages that reference this template
  </Card>
</CardGroup>
