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

# Migrate from Klaviyo to Brew

> Move profiles, consent, suppressions, segments, templates, and flows from Klaviyo into Brew, with a cutover order that avoids double-sends.

A cutover, not a sync. Profiles, consent, templates, segments, and flows move once, then nothing stays connected to Klaviyo. The rule behind every step below: **only one platform sends live email to a given contact at a time.**

This guide assumes your Brew brand and sending domain are already set up. If not, [verify your domain](/get-started/verify-your-sending-domain) first. DNS can take up to 72 hours.

<Note>
  Looking to go the other way, designing in Brew and sending from Klaviyo? See the [Klaviyo integration](/integrations/export/klaviyo) instead.
</Note>

<Tip>
  Not ready to commit? Leave your Klaviyo flows running and route one new one-off email through Brew instead. Steps 1 to 5 cover contacts and a template, which is all you need for a single test send.
</Tip>

## Concepts

| Klaviyo | Brew |
| - | - |
| Profile | Contact |
| Profile property | Custom property |
| List | Custom property plus an Audience filter, see Step 4 |
| Segment | [Audience](/audience/create-audiences), with a limit, see Step 4 |
| Email marketing consent | `Subscribed` plus a consent record, see Step 3 |
| Suppressed profile | Contact marked `Subscribed: false` |
| Campaign | [Email](/create-emails/emails) |
| Flow | [Automation](/create-emails/automations) |
| Metric (a tracked event) | A trigger event you fire, or an integration event, see Step 7 |
| Transactional flow email | Trigger plus a one-step automation on a transactional-purpose domain, see Step 6 |
| `{{ first_name\|default:'there' }}` | `{{ firstName \| there }}` |
| `{{ event.ProductName }}` | `{{ trigger.productName }}` |

## 1. Export Contacts, Consent, and Suppression

<Warning>
  Every marketing recipient must have opted in to hear from you. Importing or syncing contact data into Brew is not permission to email it. See [What You Can Send](/get-started/what-you-can-send).
</Warning>

Go to **Audience → Lists & segments**, open the list or segment, then click **Manage list → Export list to CSV**. For a segment, the menu says **Manage segment → Export segment to CSV**. Only Owner, Admin, and Analyst users see the option.

To export every profile at once, build a segment that holds all of them, then export it. Large exports take a few minutes. Files from the last 30 days wait under **Settings → Other → Downloads**.

Pick the properties to include. Keep these:

* Email, first name, last name, and any custom properties you use
* Email marketing consent, with its consent timestamp
* Email suppressions

Consent and suppression are separate in Klaviyo. A profile can show `Subscribed` and still be suppressed, for example after a hard bounce. Export both columns so you can tell.

| Email marketing consent | What to do |
| - | - |
| Subscribed | Import normally, unless the profile is suppressed |
| Unsubscribed | Import, marked `Subscribed: false` |
| Never subscribed | Leave out, or import marked `Subscribed: false` if you need the contact record for another reason |

Never subscribed is the one people miss. Klaviyo can still send these profiles some flow emails, such as abandoned cart. But they never agreed to marketing email.

Importing them as subscribed means sending marketing to people who never opted in. Leave them out of your marketing audience.

Then export the suppression list on its own. Go to **Audience → Profiles**, click **Suppressed Profiles** in the upper right, then click **Export CSV**. It holds hard bounces, repeated soft bounces, and manual suppressions.

Import every address on it as `Subscribed: false`. A contact you leave out can come back later through a form or an integration as a new, subscribed contact.

The export doesn't include list membership or event data. Export each list you still need as its own file, which Step 4 uses. Brew's engagement-based audiences start counting from your import date.

[Bring Your Consent Records](#bring-your-consent-records) covers carrying the consent timestamp over.

## 2. Map Your CSV

| Column | Required |
| - | - |
| `Email` | Yes |
| `First Name`, `Last Name` | Recommended |
| Email marketing consent | Convert it to a `Subscribed` column. `Subscribed` becomes `true`. Everything else becomes `false` |
| Anything else | Becomes a custom property automatically, except columns Brew manages such as `Created At`, which are skipped |

Notes:

* Don't import the consent column as it is. Brew reads opt-outs only from a `Subscribed` column, so any other column becomes a plain custom property and those people import as subscribed.
* Klaviyo marks its own properties with a `$` prefix on export, such as `$consent_method`. Rename the ones you keep to plain names, such as `consentMethod`, and drop the rest.
* New columns become camelCase properties. A `Plan Type` column becomes `planType`. Use that name in merge tags and filters.
* Empty cells don't overwrite existing data on update imports.
* Duplicate rows: the last one wins, so dedupe first.
* These names belong to built-in contact fields and can't be used for custom properties: `email`, `firstName`, `lastName`, `subscribed`, `createdAt`, `updatedAt`, `validationStatus`, `lastValidatedAt`, `importId`, `csvFileName`.
* Save as UTF-8. Garbled characters at the mapping step usually mean it wasn't.
* Dates: day-first dates such as `03/04/2026` (3 April) import correctly when you pick day first at the mapping step, and `YYYY-MM-DD` needs no choice. See [date formats](/audience/manage-contacts#custom-properties).

Full import behavior lives in [Add Contacts](/audience/add-contacts).

## 3. Import into Brew

**Audience → Add contacts → CSV Upload.** Map columns, review, then import. Tick the [validation checkbox](/audience/validate-contacts) first if the list hasn't been checked recently. It costs 2 credits per address, which is worth it before warming up a new domain.

Upload the subscribed contacts, the opt-outs, and the suppression list as separate files. That keeps `Subscribed` set correctly per batch.

### Bring Your Consent Records

A contact's `Subscribed` status says Brew may email them. A consent record says how and when they agreed. Carry both over.

* **Export the evidence.** Klaviyo's export includes the consent timestamp and `$consent_method`, which says how the person opted in. Keep those columns as custom properties.
* **In the app,** tick the consent checkbox at the review step. Brew records consent once for the whole file, dated the day you import.
* **To keep each person's original opt-in,** import over the API instead. `POST /v1/contacts` takes a `consent` record per contact, with the original date as `capturedAt`. It never changes `Subscribed`.

```json theme={null}
{
  "contacts": [
    {
      "email": "jane@example.com",
      "consent": { "source": "import", "capturedAt": "2024-03-02T10:15:00Z", "evidence": "Klaviyo signup form, $consent_method" }
    }
  ]
}
```

## 4. Rebuild Segmentation

[Audiences](/audience/create-audiences) are dynamic filters over contact properties, email engagement, and subscription status. Describe one in plain English or build the filters by hand.

**Segments built on what someone has done need a derived property.** Klaviyo segments can filter on any tracked metric, such as "Placed Order at least once". Brew Audiences can't filter on event history other than email engagement. To rebuild one:

1. Write the signal onto the contact as a custom property with the API (`POST /v1/contacts`), such as `orderCount`.
2. Build your Brew Audience on that property, not on the raw event.

Klaviyo's predictive analytics fields export as columns too. They import as plain properties and stop updating on the day you export.

**Lists don't have a direct equivalent.** Brew has no opt-in categories and no subscriber-facing preference center.

Opt-outs work at two levels. Each marketing domain keeps its own unsubscribe list, and **Unsubscribe from all** covers every marketing domain. See [Unsubscribes](/brand/unsubscribe#two-ways-to-opt-out).

Rebuild each list as a custom property plus an Audience filtered on it. Add a column to that list's export, such as `vipList` set to `true`, and import it.

Some people chose a list themselves. Decide how to honor that choice before your first send.

Before cutover, spot-check that a recreated Audience's membership count is in the right ballpark against the original segment. A wrong translation can undercount or overcount who gets an email, and that isn't always visible in the UI.

## 5. Rebuild Templates and Merge Tags

| You have | Use | Result |
| - | - | - |
| A template's HTML | Import email (the `</>` button) | Editable email, imports the original markup as-is |
| A Figma frame | Connect with Figma | Responsive email |
| A screenshot | Upload image to recreate | Rebuilt structure and styling |
| A live campaign | Forward to `hello@email.brew.new` | Lands in **Templates → Forwarded** |

To get the HTML, go to **Content → Templates** in Klaviyo. Open the options menu on the right of a template and click **Export**.

In Brew, open **Emails** in the sidebar, then use the import tools in the toolbar on the right side of the canvas. See [Importing existing designs](/create-emails/emails#importing-existing-designs).

For most templates, use the HTML or screenshot as reference. Prompt Brew to rebuild it against your brand rather than treating the import as finished. It's faster, and the result inherits your [brand identity](/brand/brand-identity) automatically.

Running a long parallel test instead of a fast cutover? You can design in Brew and still send from Klaviyo.

Click **Send** in the top right, choose **Export template**, and pick Klaviyo. That pushes the finished email to Klaviyo as a CODE template through the [Klaviyo integration](/integrations/export/klaviyo).

Keep each GIF under 2 MB so it loads fast on a phone.

### Translate Merge Tags

Klaviyo uses Django-style tags. Brew runs [Liquid](/create-emails/merge-tags#liquid-templating), which looks close, so most tags only need new names. Brew property names are camelCase and case-sensitive, and a snake\_case name renders empty.

If you keep an imported template's HTML as it is, replace Klaviyo's tags before you send:

* `{{ first_name|default:'there' }}` becomes `{{ firstName | there }}`. The fallback after the pipe shows for contacts with nothing on file.
* `{{ person.plan }}` becomes `{{ customer.plan }}`, or `{{ plan }}`.
* `{{ person|lookup:'Plan Type' }}` becomes the camelCase name from your import, `{{ planType }}`. Liquid has no `lookup` filter.
* `{{ event.ProductName }}` becomes `{{ trigger.productName }}`, named after the key you send in the fire's `payload`. See Step 7.
* `{% if %}` and `{% for %}` blocks keep their shape, but rename the variables inside them. Django's `{% elif %}` is `{% elsif %}` in Liquid.
* Django filters don't all exist in Liquid. Check each filter you use against the [Liquid templating](/create-emails/merge-tags#liquid-templating) reference.
* Delete `{% unsubscribe %}`, `{% manage_preferences %}`, and `{{ organization.full_address }}`. Brew adds the unsubscribe link and your mailing address to every marketing email.

## 6. Rebuild Automations

| Klaviyo flow | Brew |
| - | - |
| List trigger, such as a welcome series | Custom HTTP trigger fired from your signup form, or an integration event |
| Segment trigger | Manual-audience run, or a trigger you fire when the contact qualifies |
| Metric trigger, such as Placed Order | Integration event, such as [Shopify](/integrations/import/shopify) `orders/create`, or a custom trigger you fire, see Step 7 |
| Date property, price drop, or low inventory trigger | No built-in equivalent. Fire a custom trigger from your own code |
| Time delay | Wait |
| Conditional split, trigger split | Filter or Split |
| Multi-branch split | Several Splits in a row. A Split has two paths |
| Random sample split | Split by percentage |
| Update profile property, webhook, internal alert | No node. Do it in your own code |
| Email | Send Email, pointed at your rebuilt template |

**Transactional flow emails.** Klaviyo marks single emails in metric-triggered flows as transactional after a review. In Brew, the sending domain decides instead.

Rebuild each one as a trigger plus a one-step automation on a transactional-purpose domain. See [Transactional Email](/create-emails/transactional-email).

Test-fire before publishing. Leave every rebuilt automation unpublished until cutover day, which is what prevents a double-send. [Build an Automation](/create-emails/build-an-automation) covers the trigger setup and the test step.

## 7. Swap the API Calls in Your Code

Klaviyo's API lives at `https://a.klaviyo.com/api`. It takes a private key in a `Klaviyo-API-Key` header, a dated `revision` header, and JSON:API bodies. Brew uses one host, a bearer key, and plain JSON.

**Contact upsert.**

Before, with Klaviyo:

```js theme={null}
await fetch('https://a.klaviyo.com/api/profile-import', {
  method: 'POST',
  headers: {
    Authorization: `Klaviyo-API-Key ${process.env.KLAVIYO_PRIVATE_KEY}`,
    revision: '2026-07-15',
    'Content-Type': 'application/vnd.api+json',
  },
  body: JSON.stringify({
    data: {
      type: 'profile',
      attributes: {
        email: "user@example.com",
        first_name: "Ada",
        last_name: "Lovelace",
        properties: { plan: "pro" },
      },
    },
  }),
});
```

After, with Brew:

```js theme={null}
await fetch('https://brew.new/api/v1/contacts', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.BREW_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    email: "user@example.com",
    firstName: "Ada",
    lastName: "Lovelace",
    subscribed: true,
    customFields: { plan: "pro" },
    consent: { source: "form", capturedAt: new Date().toISOString() },
  }),
});
```

Klaviyo's profile import never changes consent. Subscribing is a second call, `POST /api/profile-subscription-bulk-create-jobs`.

In Brew, one call does both: `subscribed: true` opts the person in, and the `consent` record says how. The `consent` record never changes `subscribed`.

`subscribed: true` applies only to a new contact. A single upsert that asks for it on an opted-out contact gets `422 RESUBSCRIBE_NOT_ALLOWED`, and nothing is written. Handle that `422` by sending the same call without `subscribed`.

**Events.** A Klaviyo event can start a metric-triggered flow. In Brew that becomes a fire call: fire a trigger event, and every published automation listening for it starts.

Before, with Klaviyo:

```js theme={null}
await fetch('https://a.klaviyo.com/api/events', {
  method: 'POST',
  headers: {
    Authorization: `Klaviyo-API-Key ${process.env.KLAVIYO_PRIVATE_KEY}`,
    revision: '2026-07-15',
    'Content-Type': 'application/vnd.api+json',
  },
  body: JSON.stringify({
    data: {
      type: 'event',
      attributes: {
        metric: { data: { type: 'metric', attributes: { name: "Trial Started" } } },
        profile: { data: { type: 'profile', attributes: { email: "user@example.com" } } },
        properties: { plan: "pro" },
        unique_id: `trial-started-${subscription.id}`,
      },
    },
  }),
});
```

After, with Brew:

```js theme={null}
await fetch(
  `https://brew.new/api/v1/automations/triggers/${process.env.BREW_TRIAL_STARTED_TRIGGER_ID}/fire`,
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.BREW_API_KEY}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': `trial-started-${subscription.id}`,
    },
    body: JSON.stringify({
      payload: { email: "user@example.com", plan: "pro" },
    }),
  },
);
```

The metric name becomes the trigger you fire. Event `properties` become the fire's `payload`, read in the email as `{{ trigger.plan }}`. Use plain keys without spaces, so no `lookup` is needed.

Klaviyo's `unique_id` becomes the `Idempotency-Key` header. Keep the same stable value, like the subscription id above, so a repeat fire starts nothing new. See [Idempotency](/api-reference/api/idempotency).

Full request shapes are in the [API reference](/api-reference/api/api-introduction). This section shows the shape of the migration, not a copy-paste replacement for your code.

## 8. Cut Over

Move in this order:

1. **Rebuild while Klaviyo keeps sending.** Import contacts and rebuild segments, templates, and automations. None of it touches live sending. Keep every Brew automation unpublished.
2. **Check your segments.** Confirm each recreated Audience's membership count against the original. See Step 4.
3. **Refresh opt-outs.** Right before your first Brew send, export Klaviyo's new unsubscribes and suppressions and import them into Brew as `Subscribed: false`. Repeat before each type you move, until Klaviyo stops sending. Anyone who opted out during the rebuild stays opted out.
4. **Warm up with the first type you move.** Pause that type on Klaviyo first, so nobody gets it from both. Send to your most engaged contacts first and ramp over several days. [Gradual send](/create-emails/send-options#gradual-send) does the ramping for you. No history means no reputation, even on a verified domain.
5. **Move one type at a time.** Campaigns first, then flows. For each flow, in order: set it to Draft on Klaviyo, publish the Brew automation, then point its signup forms, integrations, or tracked events at Brew. Watch each in [analytics](/analytics/reading-analytics) before you move the next.
6. **Move transactional email last.** Before you move any, add a separate transactional-purpose subdomain (see [Transactional Email](/create-emails/transactional-email)). A fire for a new address creates a subscribed contact, so only send marketing to people who opted in. Order confirmations and resets are the highest-stakes sends. Cut them over one at a time, and test-run each automation with a real payload before you point production code at its trigger.
7. **Keep Klaviyo as a fallback.** Leave it active but silent for a couple of weeks, then downgrade or cancel. Keep SMS on Klaviyo, or move it to a dedicated provider.

Never leave the same email type live in both places at once. That's how a contact gets the same campaign twice.

## Quick Reference

| Task | Where in Brew |
| - | - |
| Import contacts | Audience → Add contacts → CSV Upload |
| Manage unsubscribes and bounces | Audience tab, contact detail view |
| Build a segment | Audience → Create Audience |
| Import a template | Emails → canvas → Import |
| Build a flow | Automations in the sidebar |
| Create a trigger event | Trigger events, or `POST /v1/automations/triggers` |
| API keys | Settings → API |

## Need Help?

Our team is ready to support you at every step of your journey with Brew. Choose the option that works best for you:

<Tabs>
  <Tab title="Self-Service Tools">
    <CardGroup cols="2">
      <Card title="Search Documentation" icon="magnifying-glass" color="#c44925">
        Type in the "Ask any question" search bar at the top left to instantly find relevant documentation pages.
      </Card>

      <Card title="ChatGPT/Claude Integration" icon="robot" color="#c44925">
        Click "Open in ChatGPT" at the top right of any page to explore it further with ChatGPT or Claude.
      </Card>
    </CardGroup>
  </Tab>

  <Tab title="Talk to Our Team">
    <CardGroup cols="2">
      <Card title="Schedule a Call" icon="calendar" color="#c44925" href="https://calendar.google.com/calendar/u/0/appointments/schedules/AcZssZ1iYoRUG1J792XQpbuQLjSRRDupr7MwraFK-HQRCtTYdBmrQi8nZu2qXfzKQigb8gbKJK3KN3-R">
        Book time with our founders for personalized guidance on strategy, best practices, or complex implementation questions.
      </Card>

      <Card title="Call Us Directly" icon="phone" color="#c44925">
        Need immediate assistance? Reach us at **+1-(332)-203-2145** for urgent issues or time-sensitive questions.
      </Card>

      <Card title="Slack Channel" icon="slack" color="#c44925">
        Our preferred support channel. You'll receive an invite after signup for direct founder support and fast responses.
      </Card>

      <Card title="Email Support" icon="envelope" color="#c44925" href="mailto:support@brew.new">
        Contact us at **[support@brew.new](mailto:support@brew.new)** for detailed inquiries or if you prefer not to use Slack.
      </Card>
    </CardGroup>
  </Tab>
</Tabs>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.