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

> Map Intercom series, messages, and contacts to Courier, and keep the help desk in place.

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 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 Fin (formerly Intercom) concepts to Courier, covers transactional and marketing sends, and lays out a phased plan.

It says "Intercom" throughout, because the product surfaces and the API still call themselves Intercom.

Intercom is a support platform, and doubling down on that. The company renamed itself Fin in May 2026, after its AI support agent. Salesforce has signed a definitive agreement to acquire it and fold that agent technology into Agentforce. Outbound messaging sits beside that focus, and the API shows where the investment went. Sends are modeled as support conversations owned by an agent seat. Push and SMS have no send endpoint. Campaign definitions can be exported but never created in code.

Reasonable tradeoffs for a help desk, poor ones for the messages your product sends. A password reset, receipt, usage alert, or onboarding sequence needs a send API, per-channel routing, provider failover, preference enforcement, and delivery logs. Threading a conversation between a customer and an agent needs almost none of that.

Most teams land on a split. Intercom keeps the help desk, and Courier takes over what your product sends. Courier's UI and API are at full parity, so every Template, Journey, Audience, and Topic can be created programmatically. A coding agent with <Doc href="/docs/resources/mcp">Courier's MCP server</Doc> can do most of the mechanical translation, using the [ready-made prompt](#migrate-with-an-ai-agent) below.

## Map Intercom concepts to Courier

| Intercom                                                                                                                 | Courier                                                                                                                                                             |
| ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Series](https://www.intercom.com/help/en/collections/2091449-outbound)                                                  | <Doc href="/docs/journeys/overview">Journeys</Doc>                                                                                                                       |
| Event-triggered outbound messages                                                                                        | <Doc href="/docs/send/overview">Send API</Doc> or <Doc href="/docs/journeys/invoke">journey triggers</Doc>                                                                    |
| [`POST /events`](https://developers.intercom.com/docs/references/rest-api/api.intercom.io/data-events) as a send trigger | Direct <Endpoint method="POST" path="/send" name="Send a message" href="/docs/api-reference/send/send-a-message" /> or <Doc href="/docs/journeys/invoke">journey invoke</Doc> |
| [`POST /messages`](https://developers.intercom.com/docs/references/rest-api/api.intercom.io/messages)                    | <Doc href="/docs/send/overview">Send API</Doc> with a template                                                                                                           |
| One-off email or post to a segment                                                                                       | <Doc href="/docs/broadcasts/overview">Broadcasts</Doc>                                                                                                                   |
| Message composer and content blocks                                                                                      | <Doc href="/docs/design/templates/design-studio">Design Studio</Doc>                                                                                                     |
| Contacts and custom attributes                                                                                           | <Doc href="/docs/recipients/overview">User profiles</Doc> (nested JSON)                                                                                                  |
| Segments                                                                                                                 | <Doc href="/docs/recipients/lists-and-audiences/audiences">Audiences</Doc>                                                                                               |
| [Subscription Types](https://developers.intercom.com/docs/references/rest-api/api.intercom.io/subscription-types)        | <Doc href="/docs/recipients/preferences/overview">Preference topics</Doc>                                                                                                |
| Mobile Push                                                                                                              | Push channel through <Doc href="/docs/integrations/overview">your providers</Doc>                                                                                        |
| SMS and WhatsApp                                                                                                         | SMS and chat channels through <Doc href="/docs/integrations/overview">your providers</Doc>                                                                               |
| Posts, News items                                                                                                        | <Doc href="/docs/in-app/overview">Courier Inbox</Doc>                                                                                                                    |
| Banners                                                                                                                  | Your own UI over <Doc href="/docs/in-app/overview">Courier Inbox</Doc> data today, in-app banners on the roadmap                                                         |
| Message frequency rules                                                                                                  | <Doc href="/docs/send/statuses#send-limits">Send limits</Doc> and <Doc href="/docs/journeys/nodes/throttle">throttle nodes</Doc>                                              |
| Access tokens                                                                                                            | <Doc href="/docs/reference/api-overview#authentication">API keys</Doc>                                                                                                   |
| Messenger, Intercom Inbox, tickets, Workflows, Help Center, Fin                                                          | Stays in Intercom                                                                                                                                                   |

### Series and Journeys

Intercom [Series](https://www.intercom.com/help/en/collections/2091449-outbound) is the multi-step campaign builder: contacts enter on a rule or an event, then move through waits, splits, and sends. It requires the Proactive Support Plus add-on.

Courier <Doc href="/docs/journeys/overview">Journeys</Doc> fill the same role, with <Doc href="/docs/journeys/invoke">four trigger types</Doc>: API invoke, inbound webhook, Segment event, and audience membership.

| Series element                        | Courier equivalent                                                                                                                  |
| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| Message node (email, push, chat, SMS) | <Doc href="/docs/journeys/nodes/send">Send node</Doc> (references a template)                                                            |
| Wait or delay                         | <Doc href="/docs/journeys/nodes/delay">Delay node</Doc>                                                                                  |
| Rules branch                          | <Doc href="/docs/journeys/nodes/branch">Branch node</Doc>                                                                                |
| Exit rules                            | <Doc href="/docs/journeys/nodes/cancel">Cancel node</Doc> + <Doc href="/docs/journeys/invoke">cancellation API</Doc>                          |
| A/B content test                      | <Doc href="/docs/journeys/experiments">Experiments</Doc> on send nodes                                                                   |
| Tag or attribute update               | <Endpoint method="PATCH" path="/profiles/{user_id}" name="Update a Profile" href="/docs/api-reference/user-profiles/update-a-profile" /> |

Two node types have no Series equivalent:

* **<Doc href="/docs/journeys/nodes/batch">Batch</Doc> and <Doc href="/docs/journeys/nodes/digest">digest</Doc> nodes** collapse many events into one message. In Intercom, you precompute roll-ups in your own backend before firing the triggering event.
* **<Doc href="/docs/journeys/nodes/fetch-data">Fetch data nodes</Doc>** call an external API mid-journey and use the response in later steps.

Journeys are also <Doc href="/docs/journeys/build">buildable through the API</Doc>. Intercom has no Series endpoint, so a Series exists only in the UI. Its one [workflow endpoint](https://developers.intercom.com/docs/references/rest-api/api.intercom.io/workflows), `GET /export/workflows/{id}`, is read-only, documented as an EU Data Act compliance export, and covers Workflows rather than Series. Either way, campaign definitions can't be versioned as code.

### Event-triggered messages and the Send API

This is the biggest change in the migration. Plan it first.

Intercom's `POST /messages` endpoint accepts `message_type` values of `in_app`, `email`, or `whatsapp`. There is no push or SMS send endpoint. Its `template` field is a message style (`plain` or `personal`), not a reference to content you built in the composer. So sending a designed message from your backend is indirect: post an event, and let a message you configured in the UI match on it.

```bash theme={null}
# Intercom: fire an event and let a pre-configured rule match it
curl -X POST https://api.intercom.io/events \
  -H "Authorization: Bearer $INTERCOM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "event_name": "password_reset_requested",
    "created_at": 1755500000,
    "user_id": "user_123",
    "metadata": { "reset_url": "https://app.example.com/r/abc123" }
  }'
```

That returns `202 Accepted` with an empty body. Your code learns the event was accepted, not that a message was created. Intercom exposes no delivery status endpoint for email, push, or in-app.

In Courier, the same send is explicit. You name the template, the recipient, and the data, and the response carries a `requestId`:

<CodeGroup>
  ```javascript Node.js theme={null}
  const { requestId } = await courier.send.message({
    message: {
      to: {
        user_id: "user_123",
      },
      template: "nt_01kx4h2jdafq8bk9aftxak4b40",
      data: {
        reset_url: "https://app.example.com/r/abc123",
      },
    },
  });
  ```

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

  ```bash cURL 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": {
          "user_id": "user_123"
        },
        "template": "nt_01kx4h2jdafq8bk9aftxak4b40",
        "data": {
          "reset_url": "https://app.example.com/r/abc123"
        }
      }
    }'
  ```

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

  ```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{
  			"reset_url": "https://app.example.com/r/abc123",
  		},
  	},
  })
  ```

  ```java Java theme={null}
  SendMessageParams params = SendMessageParams.builder()
      .message(SendMessageParams.Message.builder()
          .to(UserRecipient.builder().userId("user_123").build())
          .template("nt_01kx4h2jdafq8bk9aftxak4b40")
          .data(JsonValue.from(java.util.Map.of(
              "reset_url", "https://app.example.com/r/abc123"
          )))
          .build())
      .build();
  SendMessageResponse response = client.send().message(params);
  ```

  ```php PHP theme={null}
  $response = $client->send->message(
    message: [
      'to' => [
        'user_id' => 'user_123',
      ],
      'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
      'data' => [
        'reset_url' => 'https://app.example.com/r/abc123',
      ],
    ],
  );
  ```

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

  var response = await client.Send.Message(parameters);
  ```

  ```bash CLI wrap theme={null}
  courier send message \
    --api-key "$COURIER_API_KEY" \
    --message.to '{"user_id": "user_123"}' \
    --message.template nt_01kx4h2jdafq8bk9aftxak4b40 \
    --message.data '{"reset_url": "https://app.example.com/r/abc123"}'
  ```

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

For a single-recipient send, that `requestId` is also the message's ID. Use it to <Endpoint method="GET" path="/messages/{message_id}" name="Get message" href="/docs/api-reference/messages/get-message">get the message</Endpoint>, <Endpoint method="GET" path="/messages/{message_id}/history" name="Get message history" href="/docs/api-reference/messages/get-message-history">read its delivery history</Endpoint>, or <Endpoint method="POST" path="/messages/{message_id}/cancel" name="Cancel message" href="/docs/api-reference/messages/cancel-message">cancel it</Endpoint>. Sends to a list or audience derive a separate ID per recipient, which you look up through the <Endpoint method="GET" path="/messages" name="List messages" href="/docs/api-reference/messages/list-messages">Messages API</Endpoint>.

Several Intercom event constraints disappear with the switch. Check each against your setup:

* Intercom caps a workspace at **120 active event names**, and each event can drive at most **50 live proactive support messages**. Courier templates are addressed by ID, so there is no shared namespace to run out of.
* Intercom events are **flat**. Its API docs state that nested JSON is not supported. Courier `data` and <Doc href="/docs/recipients/overview">user profiles</Doc> both take arbitrary nested JSON, so an order with line items goes through as-is.
* Intercom **de-duplicates** events on workspace, contact, event name, and `created_at` at second granularity, and drops duplicates with a `202`. Two legitimate identical sends in the same second collapse into one. Courier treats every send request as its own message.
* New Intercom event metadata can take **up to 24 hours** to become available in the message composer and filters. Courier templates reference variables directly, so a new field is usable on the next send.
* An Intercom outbound message can carry **only one event-based rule**, and event-triggered messages are not eligible for re-notification.

### Transactional sending

Intercom supports transactional messaging on email, in-product messages, and push, and transactional email can reach unsubscribed contacts. Two things usually drive the migration:

**It is an add-on, not a mode.** Transactional and event-triggered messaging require the Proactive Support Plus add-on. Courier has no transactional subsystem to enable. Every send goes through the same pipeline, and you control preference enforcement per topic by marking a <Doc href="/docs/recipients/preferences/overview">preference topic</Doc> as required.

**Sending is on shared infrastructure.** Intercom's docs state that [dedicated IP addresses aren't available](https://www.intercom.com/help/en/articles/9744849-connect-your-email-support-channel) and that all email goes over shared IPs. There is no option to send through your own email provider. Intercom also documents activity-based sending restrictions. When triggered, these limit email to contacts seen, heard from, or engaged within the last 180 days. Event-driven email usually delivers in seconds, but can be delayed up to 30 minutes under load.

Courier sends through <Doc href="/docs/integrations/overview">your own provider accounts</Doc>. Your SendGrid, SES, Postmark, Twilio, or FCM credentials, your domains, your reputation, your dedicated IP if you have one. Courier adds orchestration on top: <Doc href="/docs/send/routing#channel-priority">channel routing</Doc>, <Doc href="/docs/send/routing#failover">provider failover</Doc>, and <Doc href="/docs/send/statuses">error handling</Doc>. If a provider degrades, you swap it in Courier without redeploying.

### Contacts, custom attributes, and user profiles

Intercom contacts carry standard fields plus custom attributes defined through [data attributes](https://developers.intercom.com/docs/references/rest-api/api.intercom.io/data-attributes). Courier <Doc href="/docs/recipients/overview">user profiles</Doc> are JSON documents keyed by `user_id`.

The translation is direct: the contact's `external_id` becomes the Courier `user_id`, channel addresses (`email`, `phone_number`, push tokens) become profile addresses, and custom attributes become profile attributes. Because profiles accept nested JSON, attribute structures you had to flatten for Intercom can keep their shape.

Load contacts with the <Endpoint method="POST" path="/bulk" name="Create a bulk job" href="/docs/api-reference/bulk/create-a-bulk-job">bulk API</Endpoint>, or create them inline: sending to an unknown `user_id` with profile data creates the user. Intercom segments become <Doc href="/docs/recipients/lists-and-audiences/audiences">audiences</Doc>, which recalculate automatically as profile attributes change.

Push tokens are the one item to plan around. Intercom collects device tokens through its mobile SDK, so they live inside Intercom rather than in your data. Register tokens with Courier's <Endpoint method="PUT" path="/users/{user_id}/tokens" name="Add multiple tokens to user" href="/docs/api-reference/device-tokens/add-multiple-tokens-to-user">device token API</Endpoint> from your app, and run both in parallel until coverage is high enough to cut over.

### Keeping Intercom for support

This migration does not touch the help desk. Messenger, the Intercom Inbox, tickets, [Workflows](https://www.intercom.com/help/en/articles/7836459-workflows-explained), Help Center, and Fin all stay where they are. One sync keeps the two in step:

1. **Your application is the source of truth** for who the user is and what they did.
2. **Into Courier**: upsert <Endpoint method="PATCH" path="/profiles/{user_id}" name="Update a Profile" href="/docs/api-reference/user-profiles/update-a-profile">profiles</Endpoint> with identifiers, channel addresses, and the attributes you segment or personalize on.
3. **Into Intercom**: keep sending the contact updates and events your support team reads for context in the Intercom Inbox.

Events you fired only to trigger outbound messages can stop. Events your agents read in the contact timeline keep going.

### Subscription Types and preferences

Intercom [Subscription Types](https://developers.intercom.com/docs/references/rest-api/api.intercom.io/subscription-types) support opt-in and opt-out consent, and their `content_types` field accepts `email` or `sms_message`, so consent covers only those two channels.

Courier <Doc href="/docs/recipients/preferences/overview">preference topics</Doc> cover every channel, including push and in-app, and Courier enforces them at send time. Each topic can be opt-in or opt-out, required, or channel-specific, so a user can keep receipts on email while turning off push for the same topic.

Map each Subscription Type to a preference topic and import the opt-out state before your first send. Intercom's `GET /contacts/{id}/subscriptions` returns only the subscriptions a contact explicitly set. Contacts sitting on a default are absent from the response, so decide each topic's default in Courier deliberately rather than inferring it.

Courier gives you a <Guide href="/docs/guides/build-a-preference-center#hosted-page">hosted preference page</Guide>, or <Guide href="/docs/guides/build-a-preference-center#embedded-component">embeddable components</Guide> to build the preference center into your product.

### Frequency capping

Intercom controls volume per message: you set how often a message can send and cap total occurrences per contact. Courier has two mechanisms that work across messages.

<Doc href="/docs/send/statuses#send-limits">Send limits</Doc> cap volume globally, per user, per subscription topic, or per tenant. The window is an hour, day, week, month, or billing period. A blocked message shows `THROTTLED` in the logs. <Doc href="/docs/journeys/nodes/throttle">Throttle nodes</Doc> cap how many times a user or the whole population passes a point in a journey.

### In-product messages

The Intercom Messenger renders Intercom's in-product surfaces: Posts, Banners, Carousels, Tooltips, Checklists, and News items. Coverage varies by surface. Banners, for example, run on desktop and mobile web but not inside iOS and Android apps.

The closest Courier equivalent is <Doc href="/docs/in-app/overview">Courier Inbox</Doc>, a notification feed with read and unread state, <Doc href="/docs/in-app/customize-the-inbox">tabs</Doc>, and <Doc href="/docs/in-app/add-toasts">toasts</Doc>. It ships as components for <Doc href="/docs/sdk-libraries/courier-react-web">React</Doc>, <Doc href="/docs/sdk-libraries/courier-js-web">JavaScript</Doc>, and mobile. A feed is not a Messenger replacement, so the mapping is not one to one:

* **Posts and News items** map cleanly to Courier Inbox messages.
* **Banners** are on the Courier roadmap as a first-class in-app surface. Until they ship, either keep banners in Intercom or render one in your own UI over Courier Inbox data. If banners are load-bearing for you, scope them separately rather than blocking the cutover on them.
* **Tooltips, Checklists, Product Tours, and Carousels** are in-product guidance and onboarding UI. Those stay in Intercom or move to a product-adoption tool. Courier does not replace them.

Billing works differently here too. Intercom meters Posts, Mobile Push, Product Tours, Carousels, and Surveys under a single "Messages Sent" metric. The allowance comes with the Proactive Support Plus add-on, so in-product messages and push draw down the same pool.

### Personalization and localization

Intercom personalizes with attribute insertion and fallbacks in the composer. Event-triggered messages can also use event metadata, limited to the first metadata keys on the event. The attribute has to exist on the contact, or the value has to arrive as flat event metadata.

Courier templates use <Doc href="/docs/design/templates/variables">variables</Doc> in single curly brackets. They resolve from four sources rather than one:

| Source    | Use it for                                                                              |
| --------- | --------------------------------------------------------------------------------------- |
| `data`    | Values you pass on the send call, like `{order.total}`                                  |
| `profile` | Attributes stored on the user, like `{profile.firstName}`                               |
| `tenant`  | Attributes stored on the account or workspace the user belongs to, like `{tenant.name}` |
| `brand`   | Themed values, like `{brand.colors.primary}`                                            |

Anything not prefixed resolves against `data`, so `{orderId}` and `{data.orderId}` are the same. Nested paths work, so `{order.shipping.carrier}` renders without flattening the payload first.

The `tenant` source has no Intercom equivalent. If you send on behalf of customer accounts, per-account values live on the <Doc href="/docs/tenants/overview">tenant</Doc>. Company name, support address, and logo stop being copied onto every user profile or passed on every send.

For localization, Intercom's documented pattern is one message per language, targeted with an audience rule on the contact's language. Courier keeps locales on a single template: set `locale` on the profile or the send request, and Courier renders the matching version. Manage translations through <Doc href="/docs/design/elemental/locales">Design Studio</Doc> or the <Endpoint method="PUT" path="/translations/{domain}/{locale}" name="Update Translations by locale" href="/docs/api-reference/translations/update-translations-by-locale">translations API</Endpoint>.

### Authentication

Intercom authenticates with a workspace access token. Its rate limit is 10,000 API calls per minute per app and 25,000 per workspace, enforced in ten-second buckets. Courier uses <Doc href="/docs/reference/api-overview#authentication">API key authentication</Doc>: a bearer token per workspace, with separate keys for test and production. Client-side surfaces like Courier Inbox use short-lived <Doc href="/docs/in-app/authenticate-users">JWTs</Doc> scoped to a single user.

## Plan the migration

Do transactional first. Those sends are already API-driven and carry the most risk if they break. They are also the ones Intercom's shared sending and 30-minute delay ceiling serve least well. Marketing sends are less urgent and take longer to rebuild.

### Phase 1: Inventory

1. **List every live outbound message and Series.** Note the trigger, channels, and audience for each. No endpoint returns Series definitions, so take these from the Outbound list filtered to live messages. For per-message volume, [`POST /export/content/data`](https://developers.intercom.com/docs/references/rest-api/api.intercom.io/data-export) exports delivery and engagement data for outbound content, Series included, over a date range. One active job per workspace.
2. **Split the list into transactional, marketing, and in-product.** Transactional is anything a user action must produce: password resets, receipts, confirmations, alerts. In-product is banners, tours, tooltips, and checklists. Those stay in Intercom or move elsewhere.
3. **List the events you fire.** Mark which ones exist only to trigger a message and which ones your support team reads in the Intercom Inbox. The first group retires. The second stays.

### Phase 2: Transactional

4. **Connect your providers.** Add your email, SMS, and push provider credentials in Courier and verify your sending domains. You are moving off shared IPs, so check domain authentication before the first production send.
5. **Set up the profile sync.** Upsert `user_id`, channel addresses, and the attributes your templates need. Start registering push tokens with Courier from your app now, in parallel with the Intercom SDK, so tokens accumulate ahead of the push cutover.
6. **Rebuild the highest-volume transactional templates** in <Doc href="/docs/design/templates/design-studio">Design Studio</Doc>, with content for each channel the message sends on.
7. **Replace event posts with send calls.** Each `POST /events` that existed only to trigger a message becomes a <Endpoint method="POST" path="/send" name="Send a message" href="/docs/api-reference/send/send-a-message" /> naming the template and passing `data`. Usually a small diff, and it removes an indirection: the call now returns an ID you can trace to delivery status.
8. **Run in parallel and compare.** Send to a test audience through both systems, diff the rendered output, then cut over one message type at a time. Watch the <Endpoint method="GET" path="/messages" name="List messages" href="/docs/api-reference/messages/list-messages">logs</Endpoint> rather than the calendar.

### Phase 3: Preferences

9. **Map Subscription Types to preference topics**, decide the default for each, and import opt-outs before any marketing send. Do this before Phase 4, not during it.

### Phase 4: Marketing

10. **Rebuild Series as journeys**, starting with onboarding. Recreate the trigger, then the nodes. Where a Series precomputed a roll-up in your backend, replace that with a <Doc href="/docs/journeys/nodes/batch">batch</Doc> or <Doc href="/docs/journeys/nodes/digest">digest</Doc> node.
11. **Convert segments to audiences.** Segment rules become audience filters on profile attributes, and membership updates itself as profiles change.
12. **Move one-off sends to <Doc href="/docs/broadcasts/overview">broadcasts</Doc>**: pick a template, pick an audience, send now or schedule it.
13. **Add frequency caps.** Per-message rules in Intercom become <Doc href="/docs/send/statuses#send-limits">send limits</Doc> that hold across every journey and broadcast.

### Phase 5: Decommission

14. **Pause the Intercom message, don't delete it,** once its Courier replacement has run clean for a full cycle.
15. **Drop the Proactive Support Plus add-on** when no live message needs it, and stop firing the events that existed only to trigger sends.

## Migrate with an AI agent

Most of this migration is mechanical: read a message definition, create the equivalent template, rewrite the call site. That suits a coding agent well.

Three things to set up first:

* **<Doc href="/docs/resources/skills">Courier Skills</Doc>** teaches the agent Courier's real APIs and patterns, so it stops guessing at endpoints:

  ```bash theme={null}
  npx skills add trycourier/courier-skills
  ```

  In Claude Code you can install it as a plugin instead, which self-updates and ships the Courier docs MCP server.

* **<Doc href="/docs/resources/mcp">Courier's MCP server</Doc>** gives the agent direct access to your workspace. It can create templates, build journeys, upsert users, and read send logs as it works. See <Doc href="/docs/get-started/build-with-ai">Build with AI</Doc> for setup in Claude Code, Cursor, Codex, and VS Code.

* **Docs for agents.** Point it at [llms-full.txt](https://www.courier.com/docs/llms-full.txt) when it needs API detail beyond this page.

Then start it with a prompt like this one. Fill in the bracketed parts.

```text theme={null}
Help me migrate our notification sending from Intercom to Courier.

Read first:
- https://www.courier.com/docs/guides/migrate/from-intercom
  (concept mapping, constraints, and the phased plan you should follow)
- https://www.courier.com/docs/llms-full.txt
  (full Courier docs, for API detail as you need it)

Our setup:
- Codebase: [path]
- Intercom access token: [env var]
- Courier: connected through the Courier MCP server

Work in this order. Stop after step 3 and wait for my review
before you create anything in Courier.

0. Setup. Install Courier Skills so you build against Courier's real
   APIs and patterns rather than guessing:
   npx skills add trycourier/courier-skills
   Then confirm the Courier MCP server is connected. If either one is
   missing, stop and tell me before going further.

1. Inventory. Intercom has no endpoint that lists Series or outbound
   messages, so I will paste in the list from the Outbound screen.
   For each item, record: name, trigger (manual, rule, or event),
   channels, audience, and the event name if it is event-triggered.
   If you need per-message volume, use POST /export/content/data.

2. Classify each item as:
   - transactional (a user action must produce it)
   - marketing (campaign or lifecycle)
   - in-product guidance (banner, tooltip, tour, checklist, carousel)
   The third group does not move to Courier. Flag it and leave it.

3. Find the send sites. Grep the codebase for Intercom POST /events
   and POST /messages calls. For each event call, decide whether it
   exists to trigger a message or only to give support agents context
   in the Intercom Inbox. Only the first group becomes a Courier send.

   Report the inventory, the classification, and the send-site list.
   Then wait.

4. Transactional first. For each transactional item: create the
   Courier template with the channels it needs, then rewrite its call
   site from an Intercom event post to a Courier /send that names the
   template and passes data. Leave the Intercom call in place behind a
   flag so we can run both and compare output.

5. Journeys. Recreate each Series through the Courier journeys API.
   Waits become delay nodes, rules branches become branch nodes,
   messages become send nodes, exit rules become cancel nodes. Where
   the Series depended on our backend precomputing a roll-up, use a
   batch or digest node instead.

Rules:
- Never send to a real user. Use a test profile for every verification send.
- Never pause, unpublish, or delete anything in Intercom. That is my call.
- If an Intercom message relies on something Courier does not have, say so
  in your report rather than approximating it.
```

Review the classification in step 2 closely. Whether a message counts as transactional decides whether its preference topic is marked required. Getting it backwards means a receipt users can unsubscribe from, or a marketing email they cannot.

## FAQ

<AccordionGroup>
  <Accordion title="Do I have to stop using Intercom?">
    Most teams keep both. Intercom keeps the help desk: Messenger, the Intercom Inbox, tickets, Help Center, Workflows, and Fin. Courier takes over the notifications your product sends. Keep sending Intercom the contact updates and events your agents read for context, and retire only the events that existed to trigger outbound messages.
  </Accordion>

  <Accordion title="What replaces Series in Courier?">
    <Doc href="/docs/journeys/overview">Journeys</Doc>. Entry rules become <Doc href="/docs/journeys/invoke">triggers</Doc>: API invoke, inbound webhook, Segment event, or audience membership. Series nodes map across too. Waits become <Doc href="/docs/journeys/nodes/delay">delay</Doc>, rules branches become <Doc href="/docs/journeys/nodes/branch">branch</Doc>, and messages become <Doc href="/docs/journeys/nodes/send">send nodes</Doc>. Journeys add <Doc href="/docs/journeys/nodes/batch">batch</Doc>, <Doc href="/docs/journeys/nodes/digest">digest</Doc>, <Doc href="/docs/journeys/nodes/throttle">throttle</Doc>, and <Doc href="/docs/journeys/nodes/fetch-data">fetch data</Doc> nodes, and they can be <Doc href="/docs/journeys/build">built through the API</Doc>.
  </Accordion>

  <Accordion title="How do I send a push notification from my backend?">
    Call the <Doc href="/docs/send/overview">send API</Doc> with the template and recipient. Intercom has no push send endpoint. Triggering push from code meant posting an event and matching it against a message configured in the UI. In Courier the send is explicit and returns an ID you can trace through to <Endpoint method="GET" path="/messages/{message_id}/history" name="Get message history" href="/docs/api-reference/messages/get-message-history">delivery history</Endpoint>.
  </Accordion>

  <Accordion title="What happens to my Intercom push tokens?">
    Tokens collected by Intercom's mobile SDK live in Intercom. Start registering tokens with Courier's <Endpoint method="PUT" path="/users/{user_id}/tokens" name="Add multiple tokens to user" href="/docs/api-reference/device-tokens/add-multiple-tokens-to-user">device token API</Endpoint> from your app while Intercom is still live. Let coverage build across a release cycle or two, then cut push over. Plan this early, since it moves at the speed of app adoption rather than your backend deploys.
  </Accordion>

  <Accordion title="Can I keep my email deliverability during the move?">
    You improve your control over it. Intercom sends over shared IPs and does not support bringing your own email provider. Courier sends through <Doc href="/docs/integrations/overview">your own provider accounts</Doc>, so your domains, reputation, and dedicated IP if you have one carry over. Authenticate your sending domain with your provider before the first production send.
  </Accordion>

  <Accordion title="What parts of Intercom does Courier not replace?">
    The help desk (Messenger, the Intercom Inbox, tickets, Workflows), the Help Center and Articles, and Fin with the AI agent surfaces. Also in-product guidance UI such as Tooltips, Product Tours, Checklists, and Carousels. In-app banners are on the Courier roadmap. Courier replaces message delivery and orchestration: transactional sends, campaigns, journeys, templates, preferences, and the in-app notification feed.
  </Accordion>
</AccordionGroup>
