> ## 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` from the Node SDK (`@trycourier/courier` v7 and later, where the client is the default import). 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.
> To translate a template, write one locale at a time with `PUT /notifications/{id}/locales/{localeId}`, which merges into the existing translations. `PUT /notifications/{id}/content` and `PUT /notifications/{id}/elements/{elementId}` overwrite translations along with the content, so send each element's `id` and `locales` back, and first remove every `checksum` and any `locales` key that starts with an underscore.
> 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 Customer.io

> Move Customer.io automations, transactional messages, segments, and subscription preferences to Courier journeys, templates, audiences, and preferences.

Plan and run a migration of your Customer.io automations, transactional messages, and subscription preferences to Courier.

> [!NOTE]
>
> Customer.io runs lifecycle marketing and transactional messages from one account. Both move to Courier: automations (formerly campaigns) become [journeys
> DOC](https://www.courier.com/docs/journeys/overview.md), transactional messages become [templates
> DOC](https://www.courier.com/docs/design/templates/overview.md) you send through the [Send API
> DOC](https://www.courier.com/docs/send/overview.md), and broadcasts become Courier [broadcasts
> DOC](https://www.courier.com/docs/broadcasts/overview.md).

## Map Customer.io concepts to Courier

| Customer.io | Courier |
| - | - |
| [Automations](https://docs.customer.io/messaging/send/automations/overview/) (formerly campaigns) | [Journeys&#xA;DOC](https://www.courier.com/docs/journeys/overview.md) |
| [Transactional messages](https://docs.customer.io/messaging/send/transactional/api/) | [Templates&#xA;DOC](https://www.courier.com/docs/design/templates/overview.md) + [Send API&#xA;DOC](https://www.courier.com/docs/send/overview.md) |
| [Broadcasts](https://docs.customer.io/messaging/send/broadcasts/overview/): one-time sends and API-triggered broadcasts | [Broadcasts&#xA;DOC](https://www.courier.com/docs/broadcasts/overview.md) |
| [Segments](https://docs.customer.io/messaging/segmentation/segments/) | [Audiences&#xA;DOC](https://www.courier.com/docs/recipients/lists-and-audiences/audiences.md) and [lists&#xA;DOC](https://www.courier.com/docs/recipients/lists-and-audiences/overview.md) |
| [People and attributes](https://docs.customer.io/messaging/people/manage/overview/) | [Users and profiles&#xA;DOC](https://www.courier.com/docs/recipients/overview.md) |
| [Objects and relationships](https://docs.customer.io/messaging/objects-data/objects/start/) | [Tenants&#xA;DOC](https://www.courier.com/docs/tenants/overview.md), profile fields, or send `data` |
| [Subscription center](https://docs.customer.io/messaging/channels/subscriptions/center/) | [Preferences&#xA;DOC](https://www.courier.com/docs/recipients/preferences/overview.md) |
| [Liquid](https://docs.customer.io/messaging/liquid/using-liquid/) | [Handlebars variables&#xA;DOC](https://www.courier.com/docs/design/templates/variables.md) |
| [In-app messages](https://docs.customer.io/messaging/channels/in-app/getting-started/) and [notification inbox](https://docs.customer.io/messaging/channels/in-app/inbox/get-started/) | [Inbox&#xA;DOC](https://www.courier.com/docs/in-app/overview.md) |
| [Reporting webhooks](https://docs.customer.io/integrations/api/webhooks/) | [Outbound webhooks&#xA;DOC](https://www.courier.com/docs/monitor/webhooks/outbound.md) |

### Automations and journeys

A Customer.io automation starts when a person matches a trigger, then runs its workflow of messages, delays, and branches. A Courier [journey
DOC](https://www.courier.com/docs/journeys/overview.md) does the same with [send
DOC](https://www.courier.com/docs/journeys/nodes/send.md), [delay
DOC](https://www.courier.com/docs/journeys/nodes/delay.md), [branch
DOC](https://www.courier.com/docs/journeys/nodes/branch.md), [fetch data
DOC](https://www.courier.com/docs/journeys/nodes/fetch-data.md), and [cancel
DOC](https://www.courier.com/docs/journeys/nodes/cancel.md) nodes.

| Automation workflow | Courier equivalent |
| - | - |
| Message (email, push, SMS, in-app) | [Send node&#xA;DOC](https://www.courier.com/docs/journeys/nodes/send.md), one channel per node |
| Delay or wait | [Delay node&#xA;DOC](https://www.courier.com/docs/journeys/nodes/delay.md) |
| Branch | [Branch node&#xA;DOC](https://www.courier.com/docs/journeys/nodes/branch.md) |
| [Send and receive data](https://docs.customer.io/messaging/send/workflows/webhooks/action/) (webhook action) | [Fetch data node&#xA;DOC](https://www.courier.com/docs/journeys/nodes/fetch-data.md) |
| [Exit conditions](https://docs.customer.io/messaging/send/automations/exit-conditions/) | [Cancel node&#xA;DOC](https://www.courier.com/docs/journeys/nodes/cancel.md), or a cancel call from your app |
| A/B test | [Experiments&#xA;DOC](https://www.courier.com/docs/journeys/experiments.md) |

Automation triggers become journey triggers: an API invoke, an inbound webhook, a Segment event, or a user joining an audience. Build journeys [in the UI or through the Journeys API
DOC](https://www.courier.com/docs/journeys/build.md). Your growth team edits content and timing on the canvas while your engineers connect the product events behind it.

One behavior to plan around: a journey run reads the recipient's profile once, when it starts. If a later step depends on something that changes during the run, like whether a trial activated, fetch it with a [fetch data node
DOC](https://www.courier.com/docs/journeys/nodes/fetch-data.md) right before the step that needs it. The [example below](#example-migrate-a-trial-activation-automation) shows how.

### Transactional messages

Customer.io transactional messages are templates your app sends with an API call. In Courier that's a [template
DOC](https://www.courier.com/docs/design/templates/overview.md) and a call to the [Send API
DOC](https://www.courier.com/docs/send/overview.md). One template can hold email, push, SMS, chat, and Inbox content, and Courier picks channels from your routing and the user's preferences.

### Segments and audiences

Courier [audiences
DOC](https://www.courier.com/docs/recipients/lists-and-audiences/audiences.md) group users by profile attributes and keep membership current as profiles change. A data-driven segment built on profile fields, like `plan = trial`, becomes an audience. A manual segment becomes a list.

Segments built on event history, account relationships, or rolling time windows are usually easier to compute in your app or warehouse. Your app then invokes the journey for the right people, or keeps a [list
DOC](https://www.courier.com/docs/recipients/lists-and-audiences/overview.md) up to date.

### People, objects, and profiles

Courier [profiles
DOC](https://www.courier.com/docs/recipients/overview.md) hold each user's email, phone, push tokens, and any custom fields you personalize with. Keep the same user ID you use in Customer.io so both systems point at the same person. Customer.io objects like companies or accounts usually map to [tenants
DOC](https://www.courier.com/docs/tenants/overview.md), which carry their own branding, defaults, and preferences.

### Subscription preferences

Customer.io stores each person's topic and channel choices in the `cio_subscription_preferences` attribute, and a global unsubscribe in `unsubscribed` ([docs](https://docs.customer.io/messaging/channels/subscriptions/manage-subscription-preferences/)). Courier [preferences
DOC](https://www.courier.com/docs/recipients/preferences/overview.md) let users opt in or out per topic and pick channels per topic, and Courier enforces them at send time. Map each Customer.io topic to a Courier subscription topic, then link each template to its topic.

Move what each person actually receives, not the raw attributes:

| In Customer.io | In Courier |
| - | - |
| Opted out of a topic | The matching topic is opted out |
| Globally unsubscribed (`unsubscribed: true`) | Opted out of every optional topic |
| No explicit choice | Set the topic's default state to match the Customer.io default, opt-in or opt-out |
| Must always receive (receipts, security alerts) | Topic set to `REQUIRED` |

Users manage their choices on Courier's [hosted preference page
GUIDE](https://www.courier.com/docs/guides/build-a-preference-center.md#hosted-page) or an [embedded preference center
GUIDE](https://www.courier.com/docs/guides/build-a-preference-center.md#embedded-component) in your app.

### Liquid and Handlebars

Customer.io templates use Liquid. Courier uses [Handlebars
DOC](https://www.courier.com/docs/design/templates/variables.md), with `{{variable}}` syntax and helpers for conditionals, loops, and formatting. Simple variables carry over with a rename: `{{customer.first_name}}` becomes `{{data.first_name}}` when you pass it in the send, or a `{{profile.*}}` variable when it's stored on the profile. Rewrite Liquid filters and tags as Handlebars helpers.

### In-app messages and Inbox

Courier [Inbox
DOC](https://www.courier.com/docs/in-app/overview.md) is an in-app notification center with drop-in components for [React
DOC](https://www.courier.com/docs/sdk-libraries/courier-react-web.md), [iOS
DOC](https://www.courier.com/docs/sdk-libraries/ios.md), [Android
DOC](https://www.courier.com/docs/sdk-libraries/android.md), and [Web Components
DOC](https://www.courier.com/docs/sdk-libraries/courier-web-components.md). It runs on the same pipeline as email, push, and SMS, so one template and one preference setting cover all of them.

## Why Courier

* **Billed per send, not per profile.** Courier counts each template sent to a recipient. A large database you message lightly doesn't drive your bill. See [billing
  DOC](https://www.courier.com/docs/workspaces/billing.md).
* **Product and lifecycle on one platform.** Transactional sends, journeys, broadcasts, Inbox, and preferences share one pipeline, one set of users, and one log.
* **Everything in the UI is on the API.** Your growth team builds in the journey canvas and Design Studio. Engineers and AI agents work on the same journeys and templates through the API, CLI, and MCP server.
* **Preferences you can embed.** A hosted page or a drop-in component, with per-topic channel choices and tenant defaults.
* **Bring your own providers, with failover.** Connect SendGrid, Postmark, Amazon SES, Twilio, Firebase FCM, APNs, and more. Configure a backup per channel and Courier [fails over
  DOC](https://www.courier.com/docs/send/routing.md) when the primary is down.
* **Full delivery logs.** [Message logs
  DOC](https://www.courier.com/docs/monitor/overview.md) trace every message from API request to provider response, with the rendered content.

## Example: migrate a trial-activation automation

A Customer.io automation that sends a welcome email, waits two days, and follows up only if the trial hasn't activated becomes one Courier journey. Rebuild it in your Test environment, check it, then switch the trigger.

### Build the journey

Create a journey with an API trigger and two data fields: `trial_id` and `first_name`.

```text theme={null}
API trigger
  → Send: welcome email
  → Delay: 2 days
  → Fetch Data: current trial status
  → Branch: data.activation_complete is equal false
      → Send: activation follow-up email
```

Shorten the [delay
DOC](https://www.courier.com/docs/journeys/nodes/delay.md) while you test. Each [Send node
DOC](https://www.courier.com/docs/journeys/nodes/send.md) delivers one channel with its own journey template. To add an Inbox reminder, add a second Send node.

[Fetch data
DOC](https://www.courier.com/docs/journeys/nodes/fetch-data.md) asks your app for the trial's current status right before the follow-up, so a customer who activated during the delay doesn't get it:

```text theme={null}
https://api.example.com/trials/{{data.trial_id}}/activation
```

Pass `{{recipient}}` in a header so your endpoint can check that the trial belongs to that user. Return the status as JSON:

```json theme={null}
{
  "activation_complete": false
}
```

Set the merge strategy to **Overwrite** so the fresh value replaces anything already in `data`. The [Branch node
DOC](https://www.courier.com/docs/journeys/nodes/branch.md) then reads it. If the request fails, the run stops before the follow-up. See [when a fetch fails
DOC](https://www.courier.com/docs/journeys/nodes/fetch-data.md#when-a-fetch-fails).

In the journey's settings, set the cancelation token to `trial-{{data.trial_id}}`. It replaces the automation's exit condition.

Publish each journey template, then publish the journey. Publishing the journey doesn't publish its templates.

### Start a run

Where your app sent the event that triggered the Customer.io automation, [invoke the journey
DOC](https://www.courier.com/docs/journeys/invoke.md) instead:

```bash theme={null}
curl -X POST https://api.courier.com/journeys/YOUR_JOURNEY_ID/invoke \
  -H "Authorization: Bearer $COURIER_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: trial-activation-trial-123" \
  -d '{
    "user_id": "user-123",
    "data": {
      "trial_id": "trial-123",
      "first_name": "Sarah"
    }
  }'
```

A retry with the same `Idempotency-Key` returns the first run instead of starting a second. Courier keeps the key for 25 hours by default.

### Cancel when the trial activates

When activation happens in your app, cancel by token:

```bash theme={null}
curl -X POST https://api.courier.com/journeys/cancel \
  -H "Authorization: Bearer $COURIER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "cancelation_token": "trial-trial-123"
  }'
```

Cancel only touches active runs, so it's safe to retry and safe to call after the run has finished. Messages already delivered stay delivered.

### Test, then switch

Check these in your Test environment:

| Case | Expected |
| - | - |
| Trial not activated | Follow-up sends after the delay |
| Same invoke retried with the same key | One run |
| Trial activates during the delay | Cancel stops the run, no follow-up |
| Status endpoint fails | Run stops before the follow-up |
| User opted out of the topic | Follow-up is blocked, and the log shows `UNSUBSCRIBED` |

Then send the event to Courier instead of Customer.io, so each event has one sender. In Customer.io, [stop the automation](https://docs.customer.io/messaging/send/automations/statuses/) and choose **Finish their journey**, so people already in it get their remaining messages and no one new enters.

## Migrate step by step

**1. Create your Courier workspace**

[Sign up](https://app.courier.com/signup) and create a workspace. Test and Production [environments
DOC](https://www.courier.com/docs/workspaces/environments.md) have separate API keys, so you can build and test without touching live traffic.

**2. Connect your providers**

Customer.io sends email through its own infrastructure unless you set up a custom SMTP server. If you have one, connect that same provider to Courier. In Courier, go to **Integrations** and connect the providers you want for each channel: an email provider like SendGrid, Postmark, or Amazon SES, plus SMS and push providers. Add a backup provider per channel for [failover
DOC](https://www.courier.com/docs/send/routing.md).

Set up your sending domain's SPF, DKIM, and DMARC records with your email provider before your first production send.

[Courier Inbox
DOC](https://www.courier.com/docs/in-app/overview.md) needs no external provider.

**3. Move your people**

Export people and the attributes your messages use from Customer.io ([export docs](https://docs.customer.io/messaging/people/manage/exporting-users/)). Create a Courier profile for each with the same user ID, through the [Profiles API](https://www.courier.com/docs/api-reference/user-profiles/replace-a-profile.md) or a [bulk import
GUIDE](https://www.courier.com/docs/guides/import-your-users.md).

```json theme={null}
{
  "profile": {
    "email": "sarah@acme-corp.com",
    "phone_number": "+15551234567",
    "custom": {
      "first_name": "Sarah",
      "plan": "trial"
    }
  }
}
```

Keep both systems in sync until cutover. Your app should write profile changes to Courier from the moment you export.

**4. Move preferences**

1. Create a [subscription topic
   DOC](https://www.courier.com/docs/recipients/preferences/overview.md) for each Customer.io topic, with a matching default state
2. Import each person's choices through the [Preferences API](https://www.courier.com/docs/api-reference/user-preferences/update-or-create-user-preferences-for-a-subscription-topic.md)
3. Link each template to its topic

Import preferences before any optional message sends from Courier. If someone unsubscribes in Customer.io after your export, carry that change over too.

**5. Recreate templates**

For each transactional message, create a [template
DOC](https://www.courier.com/docs/design/templates/overview.md) in Design Studio, add a channel for each place it sends, and convert Liquid to [Handlebars
DOC](https://www.courier.com/docs/design/templates/variables.md). Publish it.

Content inside a journey lives in that journey's Send nodes, so build it there.

**6. Rebuild automations as journeys**

Rebuild each automation as a [journey
DOC](https://www.courier.com/docs/journeys/build.md), following the [example above](#example-migrate-a-trial-activation-automation). Start with one automation that has a clear trigger and a clear goal, check it end to end, then move the rest.

Rebuild one-time sends as [broadcasts
DOC](https://www.courier.com/docs/broadcasts/overview.md) to a list or audience. An API-triggered broadcast becomes a journey you invoke for each recipient, or a broadcast to a list.

**7. Update your send calls**

Replace Customer.io transactional calls (`POST /v1/send/email` and the other `/v1/send/*` endpoints) with Courier's [Send API
DOC](https://www.courier.com/docs/send/overview.md):

```javascript Node.js theme={null}
const { requestId } = await client.send.message({
  message: {
    to: { user_id: 'user_123' },
    template: 'nt_01kx4h2jdafq8bk9aftxak4b40',
    data: {
      reset_link: 'https://app.example.com/reset/abc123',
    },
  },
});
```

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

```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": {
        "reset_link": "https://app.example.com/reset/abc123"
      }
    }
  }'
```

```ruby Ruby theme={null}
response = courier.send_.message(
  message: {
    to: { user_id: "user_123" },
    template: "nt_01kx4h2jdafq8bk9aftxak4b40",
    data: { reset_link: "https://app.example.com/reset/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_link": "https://app.example.com/reset/abc123",
		},
	},
})
```

```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(
            "reset_link", "https://app.example.com/reset/abc123")))
        .build())
    .build();
client.send().message(params);
```

```php PHP theme={null}
$response = $client->send->message(
  message: [
    'to' => ['userID' => 'user_123'],
    'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
    'data' => ['reset_link' => 'https://app.example.com/reset/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_link", JsonSerializer.SerializeToElement("https://app.example.com/reset/abc123") },
        },
    },
};

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":{"reset_link":"https://app.example.com/reset/abc123"}}'
```

```text MCP theme={null}
With Courier MCP, send my nt_01kx4h2jdafq8bk9aftxak4b40 template to user_123 with their reset link.
```

Replace the events that started automations with [journey invokes
DOC](https://www.courier.com/docs/journeys/invoke.md).

**8. Set up event streaming**

If you stream delivery events from Customer.io's reporting webhooks, set up [outbound webhooks
DOC](https://www.courier.com/docs/monitor/webhooks/outbound.md) to send message events (sent, delivered, opened, clicked) to your warehouse or analytics tools.

**9. Test and cut over**

1. Send test messages in your Test environment and check them in [Message Logs](https://app.courier.com/logs)
2. Check that preferences, routing, and rendered content match your Customer.io setup
3. Move one message type or automation at a time to your Production API key, so each event has one sender
4. Stop the matching Customer.io automation with **Finish their journey**

## Migrate with an AI agent

Most of this migration is mechanical: read an automation, rebuild it as a journey, rewrite the call site. A coding agent handles that well.

Set up three things first:

* **[Courier Skills
  DOC](https://www.courier.com/docs/resources/skills.md)** teaches the agent Courier's real APIs and patterns:

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

* **[Courier's MCP server
  DOC](https://www.courier.com/docs/resources/mcp.md)** gives the agent access to your workspace: create templates, build journeys, upsert users, and read message logs. See [Build with AI
  DOC](https://www.courier.com/docs/get-started/build-with-ai.md) 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) for 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 notifications from Customer.io to Courier.

Read first:
- https://www.courier.com/docs/guides/migrate/from-customer-io
  (concept mapping and the migration steps 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]
- Customer.io App API key: [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:
   npx skills add trycourier/courier-skills
   Then confirm the Courier MCP server is connected. If either is
   missing, stop and tell me.

1. Inventory. Use the Customer.io App API or MCP server to list every
   running automation, transactional message, and broadcast. For each, record:
   name, trigger, channels, segment or audience, delays, exit
   conditions, and subscription topic.

2. Classify each as:
   - transactional (an action in our product must produce it)
   - lifecycle (a multi-step automation)
   - one-off (a one-time send or announcement)

3. Find the send sites. Grep the codebase for Customer.io calls:
   /v1/send/ (transactional), /v1/campaigns/.../triggers (broadcasts),
   and track or identify calls to track.customer.io or cdp.customer.io
   (events and attributes that start automations). Map each call to
   the automation or transactional message it fires.

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

4. Transactional first. For each transactional message, create the
   Courier template, convert Liquid to Handlebars, then rewrite the
   call site to a Courier /send that names the template and passes
   data. Leave the Customer.io call behind a flag.

5. Automations become journeys. Rebuild each automation through the
   Courier journeys API. Delays become delay nodes, branches become
   branch nodes, messages become send nodes (one channel each), and
   exit conditions become a cancelation token plus a cancel call.

6. Preferences. Map each Customer.io subscription topic to a Courier
   subscription topic and import opt-outs before any optional send.

Rules:
- Never send to a real user. Use a test profile in the Test
  environment for every verification send.
- Never pause, stop, or delete anything in Customer.io. That is my
  call.
- If something in Customer.io has no Courier equivalent, say so in
  your report rather than approximating it.
```

Check the Liquid conversion in step 4 closely. Each `{{customer.*}}` value becomes either a profile field you sync or a value you pass in `data`. Decide which before you build the templates.

## Map the API

| Operation | Customer.io | Courier |
| - | - | - |
| Send a transactional message | `POST /v1/send/email` (also `/v1/send/push`, `/v1/send/sms`) | [Send a message](https://www.courier.com/docs/api-reference/send/send-a-message.md) |
| Start a workflow | Track an event that triggers an automation: `POST /api/v1/customers/{identifier}/events`, or Pipelines `POST /v1/track` | [Invoke a Journey](https://www.courier.com/docs/api-reference/journeys/invoke-a-journey.md) |
| Stop a workflow | Exit conditions | [Cancel Journey runs](https://www.courier.com/docs/api-reference/journeys/cancel-journey-runs.md) |
| Send to a group | `POST /v1/campaigns/{broadcast_id}/triggers` | [Send a broadcast&#xA;DOC](https://www.courier.com/docs/broadcasts/send.md) |
| Create or update a person | `PUT /api/v1/customers/{identifier}`, or Pipelines `POST /v1/identify` | [Replace a Profile](https://www.courier.com/docs/api-reference/user-profiles/replace-a-profile.md) |
| Get a person | `GET /v1/customers/{customer_id}/attributes` | [Get a Profile](https://www.courier.com/docs/api-reference/user-profiles/get-a-profile.md) |
| Update subscription preferences | Identify with the `cio_subscription_preferences` attribute | [Update or create user Preferences for a Subscription Topic](https://www.courier.com/docs/api-reference/user-preferences/update-or-create-user-preferences-for-a-subscription-topic.md) |
| Export or bulk operations | `POST /v1/exports/customers` | [Create a bulk job](https://www.courier.com/docs/api-reference/bulk/create-a-bulk-job.md) |

## FAQ

### Can I import Customer.io automations directly?

No. Rebuild each automation as a journey. Most automations map step for step, and the [example above](#example-migrate-a-trial-activation-automation) shows the pattern. An agent with the Courier MCP server can do most of the rebuilding.

### Do I need my own email provider?

Yes. Courier sends through providers you connect, like SendGrid, Postmark, or Amazon SES. You keep your provider relationship and sender reputation, and you can add a backup provider for failover.

### What should I migrate first?

Start with transactional messages. They're single sends with a clear trigger, so they're quick to rebuild and check. Then move one lifecycle automation end to end before moving the rest.
