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

> Map Knock workflows, channels, and recipients 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>;
};

Plan and run a migration from Knock to Courier.

## Map Knock concepts to Courier

| Knock                                                              | Courier                                                                                                |
| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ |
| [Channels](https://docs.knock.app/concepts/channels)               | <Doc href="/docs/integrations/overview">Integrations</Doc>                                                  |
| [Workflows](https://docs.knock.app/concepts/workflows)             | <Doc href="/docs/design/templates/overview">Templates</Doc> + <Doc href="/docs/journeys/overview">Journeys</Doc> |
| [Recipients / Users](https://docs.knock.app/concepts/users)        | <Doc href="/docs/recipients/overview">Users / Profiles</Doc>                                                |
| [Objects](https://docs.knock.app/concepts/objects)                 | <Guide href="/docs/guides/notify-everyone-watching">Entity-scoped lists</Guide> + `data` on send            |
| [Subscriptions](https://docs.knock.app/concepts/subscriptions)     | <Doc href="/docs/recipients/lists-and-audiences/lists">List subscriptions</Doc>                             |
| [Preferences](https://docs.knock.app/preferences/overview)         | <Doc href="/docs/recipients/preferences/overview">Preferences</Doc>                                         |
| [Tenants](https://docs.knock.app/concepts/tenants)                 | <Doc href="/docs/tenants/overview">Tenants</Doc>                                                            |
| [Feeds](https://docs.knock.app/integrations/in-app/knock) (in-app) | <Doc href="/docs/in-app/overview">Inbox</Doc>                                                               |
| [Commits](https://docs.knock.app/concepts/commits)                 | Publish (draft/live)                                                                                   |

### Integrations

Knock [Channels](https://docs.knock.app/concepts/channels) map to Courier <Doc href="/docs/integrations/overview">Integrations</Doc>, where you configure providers like SendGrid, Twilio, and FCM. Add several providers to one channel type and Courier <Doc href="/docs/send/routing">fails over</Doc> between them. If your primary email provider is down, the backup takes over with no code changes.

Courier supports providers across email, SMS, push, chat, and webhooks. Provider config lives in the dashboard, so adding or swapping one never needs a deploy.

### Templates and Journeys

Knock bundles content and delivery logic into one [Workflow](https://docs.knock.app/concepts/workflows). Courier splits them into two resources. This is the biggest difference between the platforms.

<Doc href="/docs/design/templates/overview">Templates</Doc> own the content. Design them visually in Design Studio, with drag-and-drop blocks for email, SMS, push, and chat. Or define them 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: <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.

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. Those are the same entry points as Knock workflow triggers.

### Users and profiles

Courier <Doc href="/docs/recipients/overview">profiles</Doc> store a recipient's email, phone, push tokens, and any custom properties you personalize with. Profiles accept nested JSON, so structured data like feature flags, team roles, or subscription tiers fits.

Create profiles ahead of time through the API, or identify users inline at send time. If you pass a `user_id` that doesn't exist yet, Courier creates the profile.

### Objects and subscriptions

Knock [Objects](https://docs.knock.app/concepts/objects) are non-user entities people subscribe to, like a document or a project. That splits into two things in Courier, and they move separately.

**The subscriber set becomes a list.** A `list_id` is a dotted namespace, so `document.doc_a1b2.watchers` names the watchers of one document. Subscribing a user creates the list, so there is no object to register first, and `list_pattern` reaches every entity of a kind in one send. <Guide href="/docs/guides/notify-everyone-watching">Notify everyone watching</Guide> walks the whole pattern.

**The entity's own attributes become `data`.** Whatever the template renders about the document, such as its title or URL, goes in `data` on the send.

So a Knock object that is both a subscriber set and a bag of attributes maps to a list plus `data`, rather than to one resource.

### Preferences

Knock's [PreferenceSet](https://docs.knock.app/preferences/overview) maps to Courier <Doc href="/docs/recipients/preferences/overview">Preferences</Doc>. Users opt out by channel, category, or notification topic, and Courier enforces that at send time. Your code needs no conditional logic.

Courier also ships a <Guide href="/docs/guides/build-a-preference-center#hosted-page">hosted preference page</Guide> you can deploy in minutes. Embeddable <Guide href="/docs/guides/build-a-preference-center#embedded-component">React components</Guide> put a preference center inside your app.

### Tenants

<Doc href="/docs/tenants/overview">Tenants</Doc> in Courier work like [Tenants](https://docs.knock.app/concepts/tenants) in Knock. Scope branding, preference defaults, and notification feeds to a customer organization, workspace, or account. Pass a `tenant_id` at send time and Courier applies that tenant's branding and preference defaults.

One difference: Courier stores branding attributes on the Tenant resource, not as a separate Brands object.

### In-app notifications

Knock's [Feeds](https://docs.knock.app/integrations/in-app/knock) map to Courier <Doc href="/docs/in-app/overview">Inbox</Doc>, a real-time notification center 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>. Inbox runs on the same pipeline as your other channels, so there's no extra provider to configure.

You get read/unread state, archiving, per-user history, and <Doc href="/docs/in-app/add-toasts">toast notifications</Doc>.

### Versioning

Knock uses a git-style [commit model](https://docs.knock.app/concepts/commits) to version dashboard changes. Courier uses draft and published states. Edit templates and journeys in draft, preview them, and publish when ready. Published versions are immutable, so you always have a snapshot of what's live.

## Why Courier

* **Content and logic stay separate.** Templates and journeys are independent. 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 needs no third-party provider. Drop in a React, iOS, or Android component and deliver on the same pipeline as email and push.
* **Automatic failover.** Configure multiple providers per channel. If SendGrid goes down, your email goes out through the backup.
* **Hosted preferences.** Ship a user-facing preference center with one config, or embed React components 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.
* **Delivery observability.** <Doc href="/docs/monitor/overview">Message Logs</Doc> track every message from API request to provider delivery, with a timeline, error details, and the rendered content.

## 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 Knock (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 Knock's in-app feed, enable <Doc href="/docs/in-app/overview">Courier Inbox</Doc>. No external provider needed.
  </Step>

  <Step title="Recreate templates">
    Knock workflows combine content and logic. In Courier, recreate the content as <Doc href="/docs/design/templates/overview">templates</Doc> in Design Studio:

    1. Create a new template for each notification type
    2. Add content blocks for each channel (email, SMS, push, etc.)
    3. Use <Doc href="/docs/design/templates/variables">`{{variable}}`</Doc> syntax for dynamic data. Courier supports the same Handlebars-style variables
    4. Publish the template to make it available for sending

    If your Knock workflows include orchestration logic (delays, conditions, batching), recreate that in <Doc href="/docs/journeys/overview">Journeys</Doc>.
  </Step>

  <Step title="Migrate user data">
    Create user profiles in Courier with the same identifiers you use in Knock. Use 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 identify users 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 Knock's PreferenceSet, recreate that structure in Courier:

    1. Define <Doc href="/docs/recipients/preferences/overview">subscription topics</Doc> that map to your Knock categories
    2. Configure default channel routing per topic
    3. Migrate user 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 send calls">
    Replace Knock's workflow trigger calls with Courier's <Doc href="/docs/send/overview">Send API</Doc>. A basic send looks like this:

    <CodeGroup>
      ```javascript Node.js theme={null}
      const { requestId } = await client.send.message({
        message: {
          to: { user_id: 'user_123' },
          template: 'nt_01kx4h2jdafq8bk9aftxak4b40',
          data: {
            name: 'Sarah Bennett',
            action_url: 'https://app.example.com',
          },
        },
      });
      ```

      ```python Python theme={null}
      response = client.send.message(
          message={
              "to": {"user_id": "user_123"},
              "template": "nt_01kx4h2jdafq8bk9aftxak4b40",
              "data": {
                  "name": "Sarah Bennett",
                  "action_url": "https://app.example.com",
              },
          },
      )
      ```

      ```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": {
              "name": "Sarah Bennett",
              "action_url": "https://app.example.com"
            }
          }
        }'
      ```

      ```ruby Ruby theme={null}
      response = courier.send_.message(
        message: {
          to: { user_id: "user_123" },
          template: "nt_01kx4h2jdafq8bk9aftxak4b40",
          data: { name: "Sarah Bennett", action_url: "https://app.example.com" }
        }
      )
      ```

      ```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{
      			"name": "Sarah Bennett",
      			"action_url": "https://app.example.com",
      		},
      	},
      })
      ```

      ```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(
                  "name", "Sarah Bennett",
                  "action_url", "https://app.example.com")))
              .build())
          .build();
      client.send().message(params);
      ```

      ```php PHP theme={null}
      $response = $client->send->message(
        message: [
          'to' => ['userID' => 'user_123'],
          'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
          'data' => ['name' => 'Sarah Bennett', 'action_url' => 'https://app.example.com'],
        ],
      );
      ```

      ```csharp C# theme={null}
      SendMessageParams parameters = new()
      {
          Message = new()
          {
              To = new UserRecipient { UserID = "user_123" },
              Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
              Data = new Dictionary<string, JsonElement>()
              {
                  { "name", JsonSerializer.SerializeToElement("Sarah Bennett") },
                  { "action_url", JsonSerializer.SerializeToElement("https://app.example.com") },
              },
          },
      };

      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":{"name":"Sarah Bennett","action_url":"https://app.example.com"}}'
      ```

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

    Courier applies routing, preferences, and failover from 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 Knock 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            | Knock                                | Courier                                                                                                                                                                                                                                      |
| -------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Send a notification  | `POST /workflows/:key/trigger`       | <Endpoint method="POST" path="/send" name="Send a message" href="/docs/api-reference/send/send-a-message" />                                                                                                                                      |
| Create/update a user | `PUT /users/:id`                     | <Endpoint method="PUT" path="/profiles/{user_id}" name="Replace a Profile" href="/docs/api-reference/user-profiles/replace-a-profile" />                                                                                                          |
| Get a user           | `GET /users/:id`                     | <Endpoint method="GET" path="/profiles/{user_id}" name="Get a Profile" href="/docs/api-reference/user-profiles/get-a-profile" />                                                                                                                  |
| Set user preferences | `PUT /users/:id/preferences/:set_id` | <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 /messages/:id`                  | <Endpoint method="GET" path="/messages/{message_id}" name="Get message" href="/docs/api-reference/messages/get-message" />                                                                                                                        |
| List messages        | `GET /messages`                      | <Endpoint method="GET" path="/messages" name="List messages" href="/docs/api-reference/messages/list-messages" />                                                                                                                                 |
| Create/update tenant | `PUT /tenants/:id`                   | <Endpoint method="PUT" path="/tenants/{tenant_id}" name="Create or replace a Tenant" href="/docs/api-reference/tenants/create-or-replace-a-tenant" />                                                                                             |
