> ## 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.

> ## Agent Instructions
> Install the Courier skill before writing code: `npx skills add trycourier/courier-skills`. It carries the verified SDK shapes and the rules you cannot get wrong.
> Authenticate every request with `Authorization: Bearer <API_KEY>`. A workspace has several environments and each has its own keys, which are plain `pk_` strings with no environment prefix. Start with Test.
> Send with `client.send.message`, the default import of the v7 Node SDK. Reference a template by its `nt_` id or its alias.
> A send accepts a bare Elemental element list, but storing content on a template requires the top-level elements wrapped in a channel element.
> Templates and journeys can be built in the Courier app or created through the API. Either way they live in the workspace and are referenced by ID when you send.
> The hosted MCP server is https://mcp.courier.com. For a briefing on what Courier is and when to use it, read https://www.courier.com/llms.txt.
> Prefer the Guides tab for how-do-I questions and the Docs tab for how-does-it-behave questions. The API reference lives under /api-reference.

# Migrate from Novu

> Map Novu workflows, steps, and subscribers to Courier, with an API mapping and a plan.

export const Endpoint = ({method, path, name, href, children, bare}) => {
  const verb = String(method || "").toUpperCase();
  const title = verb + " " + path;
  const label = children || name || path;
  if (bare) {
    return href ? <a href={href}><code>{title}</code></a> : <code>{title}</code>;
  }
  if (!href) {
    return <span className="cx-endpoint" data-method={verb} title={title}>
        <span className="cx-endpoint-label">{label}</span>
        <span className="cx-endpoint-method">{verb}</span>
      </span>;
  }
  return <a className="cx-endpoint" data-method={verb} href={href} title={title}>
      <span className="cx-endpoint-label">{label}</span>
      <span className="cx-endpoint-method">{verb}</span>
    </a>;
};

export const AppLink = ({href, children, name, bare}) => {
  const label = children || name || "Open in Courier";
  if (bare) {
    return <a href={href} target="_blank" rel="noreferrer">{label}</a>;
  }
  return <a className="cx-endpoint" data-kind="app" href={href} target="_blank" rel="noreferrer">
      <span className="cx-endpoint-label">{label}</span>
      <span className="cx-endpoint-method" aria-hidden="true">↗</span>
    </a>;
};

export const Guide = ({href, children, name, bare}) => {
  const label = children || name || href;
  if (bare) {
    return <a href={href}>{label}</a>;
  }
  return <a className="cx-endpoint" data-kind="guide" href={href}>
      <span className="cx-endpoint-label">{label}</span>
      <span className="cx-endpoint-method">GUIDE</span>
    </a>;
};

export const Doc = ({href, children, name, bare}) => {
  const label = children || name || href;
  if (bare) {
    return <a href={href}>{label}</a>;
  }
  return <a className="cx-endpoint" data-kind="doc" href={href}>
      <span className="cx-endpoint-label">{label}</span>
      <span className="cx-endpoint-method">DOC</span>
    </a>;
};

This guide maps Novu concepts to Courier and gives you a step-by-step migration plan.

## Map Novu concepts to Courier

| Novu                                                                                                   | Courier                                                                                                |
| ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ |
| [Workflows](https://docs.novu.co/platform/concepts/workflows)                                          | <Doc href="/docs/design/templates/overview">Templates</Doc> + <Doc href="/docs/journeys/overview">Journeys</Doc> |
| [Subscribers](https://docs.novu.co/platform/concepts/subscribers)                                      | <Doc href="/docs/recipients/overview">Users / Profiles</Doc>                                                |
| [Topics](https://docs.novu.co/platform/concepts/topics)                                                | <Doc href="/docs/recipients/preferences/overview">Subscription Topics</Doc>                                 |
| [Integrations](https://docs.novu.co/integrations/overview)                                             | <Doc href="/docs/integrations/overview">Integrations</Doc>                                                  |
| [Digest](https://docs.novu.co/platform/workflow/add-and-configure-steps/configure-action-steps/digest) | <Doc href="/docs/journeys/nodes/batch">Batch</Doc> + <Doc href="/docs/journeys/nodes/digest">Digest</Doc> nodes  |
| [Delay](https://docs.novu.co/platform/workflow/add-and-configure-steps/configure-action-steps/delay)   | <Doc href="/docs/journeys/nodes/delay">Delay node</Doc>                                                     |
| [Preferences](https://docs.novu.co/platform/concepts/preferences)                                      | <Doc href="/docs/recipients/preferences/overview">Preferences</Doc>                                         |
| [Inbox](https://docs.novu.co/inbox/overview)                                                           | <Doc href="/docs/in-app/overview">Inbox</Doc>                                                               |
| [Tenants](https://docs.novu.co/platform/concepts/tenants)                                              | <Doc href="/docs/tenants/overview">Tenants</Doc>                                                            |

### Workflows, templates, and Journeys

Novu [workflows](https://docs.novu.co/platform/concepts/workflows) combine content, channel routing, and orchestration logic (digest, delay, conditions) in one resource. Courier splits these into two independent pieces. That is the biggest architectural difference between the platforms.

<Doc href="/docs/design/templates/overview">Templates</Doc> own the content layer. Design them visually in Design Studio (drag-and-drop blocks for email, SMS, push, and chat) or in code with <Doc href="/docs/design/elemental/overview">Elemental</Doc> JSON. Either way, your product team ships copy changes without an engineering cycle.

<Doc href="/docs/journeys/overview">Journeys</Doc> own the orchestration layer: <Doc href="/docs/journeys/nodes/delay">delays</Doc>, <Doc href="/docs/journeys/nodes/branch">branching</Doc>, <Doc href="/docs/journeys/nodes/batch">batching</Doc>, <Doc href="/docs/journeys/nodes/digest">digests</Doc>, and <Doc href="/docs/journeys/nodes/cancel">cancellation</Doc>. A journey's <Doc href="/docs/journeys/nodes/send">send node</Doc> references a template by ID, so the two evolve independently.

| Novu step type                        | Courier equivalent                                                                                                                         |
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| Channel step (email, SMS, push, chat) | <Doc href="/docs/journeys/nodes/send">Send node</Doc> (references a template)                                                                   |
| Digest step                           | <Doc href="/docs/journeys/nodes/batch">Batch node</Doc> (window-based) or <Doc href="/docs/journeys/nodes/digest">Digest node</Doc> (schedule-based) |
| Delay step                            | <Doc href="/docs/journeys/nodes/delay">Delay node</Doc>                                                                                         |
| Custom step                           | <Doc href="/docs/journeys/nodes/fetch-data">Fetch data node</Doc> or <Doc href="/docs/journeys/nodes/branch">branch</Doc> logic                      |

A journey starts from a <Doc href="/docs/journeys/invoke">trigger</Doc>: an API invoke, an inbound webhook, a Segment event, or a user joining an audience. Build journeys visually <Doc href="/docs/journeys/build">in the UI</Doc> or through the <Doc href="/docs/journeys/build">Journeys API</Doc>.

### Subscribers and users

Novu [subscribers](https://docs.novu.co/platform/concepts/subscribers) map to Courier <Doc href="/docs/recipients/overview">profiles</Doc>. Both store email, phone, push tokens, locale, and custom properties.

Courier profiles accept nested JSON, so account tiers, team roles, and feature flags fit as-is. Create profiles ahead of time through the API, or identify users inline at send time. Pass a `user_id` that doesn't exist yet and Courier creates the profile.

### Topics and subscription topics

Novu [topics](https://docs.novu.co/platform/concepts/topics) group subscribers for bulk delivery. The Courier equivalent depends on your use case:

* **For notification categories** (letting users opt in/out of types of notifications): use <Doc href="/docs/recipients/preferences/overview">Subscription Topics</Doc> within Preferences
* **For bulk delivery to groups**: use <Endpoint method="GET" path="/lists" name="List Lists" href="/docs/api-reference/lists/list-lists">Lists</Endpoint> or <Doc href="/docs/recipients/lists-and-audiences/audiences">Audiences</Doc>

### Integrations

Novu and Courier both call provider connections "integrations." Courier supports providers across email, SMS, push, chat, and webhooks. Wire up multiple providers for one channel type and Courier <Doc href="/docs/send/routing">fails over</Doc> between them automatically, with no code changes.

### Digest and delay

Courier's <Doc href="/docs/journeys/nodes/batch">Batch node</Doc> covers Novu's [digest step](https://docs.novu.co/platform/workflow/add-and-configure-steps/configure-action-steps/digest) with a time window. It collects matching events, then releases them as one aggregated payload for a single summary notification. Your template receives the full list of collected events. For summaries that release on a fixed schedule the user controls (a daily activity email, a weekly report), use the <Doc href="/docs/journeys/nodes/digest">Digest node</Doc> instead.

<Doc href="/docs/journeys/nodes/delay">Delay</Doc> works like Novu's delay step. Specify a duration and the journey pauses before the next node.

### Preferences

Novu's [preference system](https://docs.novu.co/platform/concepts/preferences) supports global and per-workflow channel controls. Courier <Doc href="/docs/recipients/preferences/overview">Preferences</Doc> support the same hierarchy (global, per-topic, per-channel) and enforce it at send time.

Courier also ships a <Guide href="/docs/guides/build-a-preference-center#hosted-page">hosted preference page</Guide> you can deploy in minutes, plus embeddable <Guide href="/docs/guides/build-a-preference-center#embedded-component">React components</Guide> for in-app preference centers. No custom UI required.

### In-app notifications

Novu's [Inbox](https://docs.novu.co/inbox/overview) component handles in-app notifications. Courier <Doc href="/docs/in-app/overview">Inbox</Doc> does the same, with drop-in components for <Doc href="/docs/sdk-libraries/courier-react-web">React</Doc>, <Doc href="/docs/sdk-libraries/ios">iOS</Doc>, <Doc href="/docs/sdk-libraries/android">Android</Doc>, and <Doc href="/docs/sdk-libraries/courier-js-web">vanilla JS</Doc>.

Courier Inbox runs on the same delivery pipeline as email, push, and SMS. There's no separate service to manage. You get read/unread state, archiving, per-user history, <Doc href="/docs/in-app/add-toasts">toast notifications</Doc>, and <Doc href="/docs/in-app/customize-the-inbox">tab-based organization</Doc>.

### Tenants

Courier <Doc href="/docs/tenants/overview">tenants</Doc> work like Novu [tenants](https://docs.novu.co/platform/concepts/tenants). Scope branding, preference defaults, and notification feeds to one customer organization. Pass a `tenant_id` at send time and Courier applies that tenant's branding and preferences.

## Why Courier

* **Fully managed infrastructure.** No self-hosting to maintain, no bridge endpoints to deploy. Courier handles orchestration, delivery, retries, and scaling.
* **Content and logic stay separate.** Templates and journeys are independent resources. Your product team updates copy in Design Studio while engineers tune journey timing.
* **Design Studio.** Build email, SMS, push, and chat content with drag-and-drop blocks. Preview across channels, personalize with variables, and publish without deploying code.
* **Built-in in-app channel.** Courier Inbox works without a third-party provider. Drop in a React, iOS, or Android component and deliver in-app notifications on the same pipeline as email and push.
* **Automatic failover.** Configure multiple providers per channel and Courier fails over automatically. If SendGrid goes down, your email still goes out through your backup provider.
* **Hosted preferences out of the box.** Ship a user-facing preference center with a single config, or embed React components directly in your app. No custom UI required.
* **The providers you already use.** Email, SMS, push, chat, webhooks, CDPs, and observability tools. Switch providers without changing your send code.
* **Full delivery observability.** <Doc href="/docs/monitor/overview">Message Logs</Doc> track every message from API request to provider delivery with a detailed timeline, error details, and rendered content inspection.

## Migrate step by step

<Steps>
  <Step title="Create your Courier workspace">
    <AppLink href="https://app.courier.com/signup">Sign up</AppLink> and create a workspace. Courier gives you separate Test and Production <Doc href="/docs/workspaces/overview#environments-and-api-keys">environments</Doc> with their own API keys, so you can migrate without touching live traffic.
  </Step>

  <Step title="Configure integrations">
    Go to **Integrations** in your Courier dashboard and connect the same providers you use in Novu (SendGrid, Twilio, FCM, etc.). Each provider maps to a channel type (email, SMS, push, chat). Configure multiple providers per channel for <Doc href="/docs/send/routing">failover</Doc>.

    If you use Novu's inbox component, enable <Doc href="/docs/in-app/overview">Courier Inbox</Doc>. No external provider needed.
  </Step>

  <Step title="Recreate templates">
    Novu workflows contain inline content per channel step. In Courier, extract the content into <Doc href="/docs/design/templates/overview">templates</Doc>:

    1. Create a new template for each notification type
    2. Add content blocks for each channel (email body, SMS text, push title/body, etc.)
    3. Use `{{variable}}` syntax for dynamic data. Both platforms use the same Handlebars-style approach
    4. Publish the template to make it available for sending
  </Step>

  <Step title="Recreate workflows as journeys">
    If your Novu workflows include digest, delay, or conditional logic, recreate them in <Doc href="/docs/journeys/overview">Journeys</Doc>. Build journeys visually <Doc href="/docs/journeys/build">in the UI</Doc> or programmatically via the <Doc href="/docs/journeys/build">Journeys API</Doc>.

    For notification types that need no orchestration (no delays, no digests), skip journeys and send directly via the <Doc href="/docs/send/overview">Send API</Doc>.
  </Step>

  <Step title="Migrate subscriber data">
    Create user profiles in Courier with the same identifiers you use in Novu, via the <Endpoint method="POST" path="/profiles/{user_id}" name="Create a Profile" href="/docs/api-reference/user-profiles/create-a-profile">Profiles API</Endpoint> or inline at send time.

    ```json theme={null}
    {
      "user_id": "user_123",
      "profile": {
        "email": "sarah@acme-corp.com",
        "phone_number": "+15551234567",
        "custom": {
          "name": "Sarah Bennett",
          "plan": "enterprise"
        }
      }
    }
    ```
  </Step>

  <Step title="Set up preferences">
    If you use Novu's subscriber preferences, recreate your structure in Courier:

    1. Define <Doc href="/docs/recipients/preferences/overview">subscription topics</Doc> that map to your Novu workflow categories
    2. Configure default channel routing per topic
    3. Migrate subscriber preference selections via the <Endpoint method="GET" path="/users/{user_id}/preferences" name="Get user's Preferences" href="/docs/api-reference/user-preferences/get-users-preferences">Preferences API</Endpoint>

    Courier also provides a <Guide href="/docs/guides/build-a-preference-center#hosted-page">hosted preference page</Guide> you can deploy immediately, or <Guide href="/docs/guides/build-a-preference-center#embedded-component">React components</Guide> to embed preferences in your app.
  </Step>

  <Step title="Update your trigger calls">
    Replace Novu's workflow trigger calls with Courier's <Doc href="/docs/send/overview">Send API</Doc>:

    <CodeGroup>
      ```javascript Node.js theme={null}
      const { requestId } = await client.send.message({
        message: {
          to: { user_id: 'user_123' },
          template: 'nt_01kx4h2jdafq8bk9aftxak4b40',
          data: {
            order_id: 'ORD-456',
            total: 79.99,
          },
        },
      });
      ```

      ```python Python theme={null}
      response = client.send.message(
          message={
              "to": {"user_id": "user_123"},
              "template": "nt_01kx4h2jdafq8bk9aftxak4b40",
              "data": {
                  "order_id": "ORD-456",
                  "total": 79.99,
              },
          },
      )
      ```

      ```bash cURL theme={null}
      curl -X POST https://api.courier.com/send \
        -H "Authorization: Bearer $COURIER_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "message": {
            "to": { "user_id": "user_123" },
            "template": "nt_01kx4h2jdafq8bk9aftxak4b40",
            "data": {
              "order_id": "ORD-456",
              "total": 79.99
            }
          }
        }'
      ```

      ```ruby Ruby theme={null}
      response = courier.send_.message(
        message: {
          to: { user_id: "user_123" },
          template: "nt_01kx4h2jdafq8bk9aftxak4b40",
          data: { order_id: "ORD-456", total: 79.99 }
        }
      )
      ```

      ```go Go theme={null}
      response, err := client.Send.Message(context.TODO(), courier.SendMessageParams{
      	Message: courier.SendMessageParamsMessage{
      		To: courier.SendMessageParamsMessageToUnion{
      			OfUserRecipient: &shared.UserRecipientParam{
      				UserID: courier.String("user_123"),
      			},
      		},
      		Template: courier.String("nt_01kx4h2jdafq8bk9aftxak4b40"),
      		Data: map[string]any{
      			"order_id": "ORD-456",
      			"total": 79.99,
      		},
      	},
      })
      ```

      ```java Java theme={null}
      SendMessageParams params = SendMessageParams.builder()
          .message(SendMessageParams.Message.builder()
              .to(JsonValue.from(java.util.Map.of("user_id", "user_123")))
              .template("nt_01kx4h2jdafq8bk9aftxak4b40")
              .data(JsonValue.from(java.util.Map.of(
                  "order_id", "ORD-456",
                  "total", 79.99)))
              .build())
          .build();
      client.send().message(params);
      ```

      ```php PHP theme={null}
      $response = $client->send->message(
        message: [
          'to' => ['userID' => 'user_123'],
          'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
          'data' => ['order_id' => 'ORD-456', 'total' => 79.99],
        ],
      );
      ```

      ```csharp C# theme={null}
      SendMessageParams parameters = new()
      {
          Message = new()
          {
              To = new UserRecipient { UserID = "user_123" },
              Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
              Data = new Dictionary<string, JsonElement>()
              {
                  { "order_id", JsonSerializer.SerializeToElement("ORD-456") },
                  { "total", JsonSerializer.SerializeToElement(79.99) },
              },
          },
      };

      await client.Send.Message(parameters);
      ```

      ```bash CLI theme={null}
      courier send message \
        --api-key "$COURIER_API_KEY" \
        --message '{"to":{"user_id":"user_123"},"template":"nt_01kx4h2jdafq8bk9aftxak4b40","data":{"order_id":"ORD-456","total":79.99}}'
      ```

      ```text MCP theme={null}
      With Courier MCP, send my nt_01kx4h2jdafq8bk9aftxak4b40 template to user_123 with the order details.
      ```
    </CodeGroup>

    Courier handles routing, preferences, and failover automatically based on your template and workspace configuration.
  </Step>

  <Step title="Test and cut over">
    1. Send test messages in your Test environment and verify delivery in <AppLink href="https://app.courier.com/logs">Message Logs</AppLink>
    2. Validate that preferences, routing, and template rendering match your Novu setup
    3. Switch your production code to use Courier's Production API key
    4. Monitor <Doc href="/docs/monitor/overview">Message Logs</Doc> and <Doc href="/docs/monitor/overview">Analytics</Doc> for delivery confirmation
  </Step>
</Steps>

## Map the API

| Operation                  | Novu                                    | Courier                                                                                                                                                                                                                                      |
| -------------------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Send a notification        | `POST /v1/events/trigger`               | <Endpoint method="POST" path="/send" name="Send a message" href="/docs/api-reference/send/send-a-message" />                                                                                                                                      |
| Create/update a subscriber | `PUT /v1/subscribers/:id`               | <Endpoint method="PUT" path="/profiles/{user_id}" name="Replace a Profile" href="/docs/api-reference/user-profiles/replace-a-profile" />                                                                                                          |
| Get a subscriber           | `GET /v1/subscribers/:id`               | <Endpoint method="GET" path="/profiles/{user_id}" name="Get a Profile" href="/docs/api-reference/user-profiles/get-a-profile" />                                                                                                                  |
| Set preferences            | `PATCH /v2/subscribers/:id/preferences` | <Endpoint method="PUT" path="/users/{user_id}/preferences/{topic_id}" name="Update or create user Preferences for a Subscription Topic" href="/docs/api-reference/user-preferences/update-or-create-user-preferences-for-a-subscription-topic" /> |
| Get message status         | `GET /v1/messages/:id`                  | <Endpoint method="GET" path="/messages/{message_id}" name="Get message" href="/docs/api-reference/messages/get-message" />                                                                                                                        |
| List messages              | `GET /v1/messages`                      | <Endpoint method="GET" path="/messages" name="List messages" href="/docs/api-reference/messages/list-messages" />                                                                                                                                 |
| Manage topics              | `POST /v1/topics`                       | <Endpoint method="PUT" path="/lists/{list_id}" name="Update a List" href="/docs/api-reference/lists/update-a-list" />                                                                                                                             |
