Skip to main content
Looking to go the other way, designing in Brew and sending from Loops? See the Loops integration instead.
Not ready to commit? Leave your sequences in Loops 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.
Loops customers are usually more code-integrated than a typical marketing-tool switch. Events and per-recipient sends are often wired straight into application code through the API. Campaigns, workflows, themes and email messages are also reachable over the Loops API, CLI and MCP server. Both halves move. Campaigns and workflows become Emails and Automations. Step 7 covers the code swap for both kinds of call. A Loops transactional email becomes a trigger plus a one-step automation sending from a transactional-purpose domain. That domain skips the unsubscribe footer and delivers to unsubscribed contacts. This guide assumes your Brew brand and sending domain are already set up. If not, verify your domain first. DNS can take up to 72 hours. Rule for the whole migration: only one platform sends live email to a given contact at a time.

Concepts

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.
On the Audience page, filter to a segment if you want a subset. Click the ••• menu at the top right of the filter box, then Download. Loops writes one CSV of whatever’s in view, with columns for default and custom properties only. There’s no separate export per subscription status, so export everyone, unsubscribed contacts included. Importing them with Subscribed: false keeps Brew’s marketing from reaching them. Leaving them out lets a later import add them back as subscribed. Carries over cleanly: email, name, source, custom properties, and subscribed (true/false). Mailing list membership doesn’t carry over automatically. Loops’ export includes contact properties, not list membership. A straight export and import won’t preserve which lists someone belonged to. If that matters, filter to each list and export it separately, then reconstruct it in Step 4. Event history and workflow enrollment state don’t carry over either. Those live in Loops with no Brew equivalent to import into. In Loops, unsubscribing blocks campaigns and workflows. Brew’s marketing sends respect the same flag, so the behavior your subscribed contacts expect doesn’t change. Keep any opt-in date and source columns in the export. Bring Your Consent Records covers carrying them over.

2. Map Your CSV

Notes:
  • userGroup and any custom properties become Brew custom properties. Bring userId too if anything downstream matches on it.
  • Save as UTF-8. Garbled characters at the mapping step usually mean it wasn’t.
  • Mailing list membership needs its own export per list (see Step 1), then its own import into the matching property. It won’t ride along with a general contact import.
  • 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.
For how an import handles empty cells, duplicate rows, and columns Brew manages such as Created At, see Add Contacts. Custom properties can’t use the names of Brew’s built-in contact fields, listed in Custom Properties.

3. Import into Brew

Audience → Add contacts → CSV Upload. Map columns, review, then import. Tick the validation checkbox first if the list hasn’t been checked recently. It costs credits, which is worth it before warming up a new domain. Validate Contacts has the cost and Add Contacts the full import behavior. 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. Include the source column and any opt-in date your Loops export carries. 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 date, import over the API instead, with a consent record per contact. See Add Contacts for the consent fields.

4. Rebuild Segmentation

Filters and Segments map to Brew Audiences, dynamic filters over contact properties, email engagement, and subscription status. Describe one in plain English or build the filters by hand. Mailing Lists don’t have a direct equivalent. Rebuild each list as a custom property plus an Audience filtered on it. Lists and Event-Based Segments covers what changes. To carry over opt-outs from a specific sender, import them into that domain’s list.

5. Rebuild Templates and Merge Tags

Open Emails in the sidebar, then use the Import tools on the right side of the canvas. See Importing existing designs. You don’t have to screenshot a campaign. GET /v1/email-messages/{id} on the Loops API returns its compiled LMX content. Pull the design by API and hand it to Brew as a prompt. Loops themes, the shared styling you apply across emails, don’t carry over as a concept. Brew’s brand identity replaces the need for a manually maintained theme. Set it once and every rebuilt template inherits your logo, colors, fonts, and voice.

Translate Merge Tags

Loops uses single curly braces. {firstName} works in campaigns and workflows, and {EVENT_PROPERTY:name} in workflow emails triggered by events. The first becomes Brew’s {{ firstName | there }}. The property name is case-sensitive, and the fallback shows when the value is missing. The second becomes a trigger payload reference: {EVENT_PROPERTY:name} is {{ trigger.name }}. See merge tags. {DATA_VARIABLE:name} lives in Loops transactional emails. In Brew it’s a trigger payload reference like the rest: {{ name }}, or {{ trigger.name }} for the namespaced form. In Loops, a missing property value with no fallback means the email doesn’t send. In Brew, a tag with no value renders empty and the email still goes out. Add a fallback, like {{ firstName | there }}, to every tag that might be missing.

6. Rebuild Automations

Loops has a trick for re-running existing contacts through a workflow. It only reaches Contact added and Contact updated triggers, so don’t expect other workflows to re-fire. Loops’ own recipe duplicates the workflow with a Contact updated trigger on a temporary property and a one-time frequency. You then re-upload a CSV with that property set and “Trigger workflows” on. That maps to a manual-audience run in Brew, the one-off, on-demand option, not a live trigger. Manual-audience automations are launched with POST /v1/automations/{automationId}/run rather than published. Branches become Filter or Split nodes, and delay steps become Wait nodes. Test-fire before publishing, and leave every rebuilt automation unpublished until cutover. That’s what prevents a double-send.

7. Swap the API Calls in Your Code

Your Loops code has two kinds of call, and both land on the same fire endpoint. sendEvent calls become trigger fires. A cart-abandonment or re-engagement email is a marketing send fired per recipient by a custom event. In Brew that’s a trigger event plus an automation, and every per-recipient marketing send goes through it. sendTransactionalEmail calls move the same way. A reset, magic link, or receipt becomes a trigger plus a one-step automation. Its Send Email node uses a transactional-purpose sending domain, which skips the unsubscribe footer and delivers to unsubscribed contacts. transactionalId maps to the triggerEventId you fire. Each dataVariables key becomes a payload field the email references as {{ trigger.* }}. The setup and fire call are identical to the marketing events below; only the domain on the send node differs. Set each marketing event up once: trigger, opt-in Filter, email, Send Email node on a verified domain, publish. Build an Automation walks the API calls. Then replace the Loops sendEvent call in your backend with a fire request. The Filter checks your opt-in property, such as contact.marketingOptIn. Send Marketing Only to Contacts Who Opted In shows how to get it onto the contact. Before, with Loops:
After, with Brew:
Set an Idempotency-Key on every retried call, the same discipline you used in Loops. Derive it from something stable about the action, like the subscription id above. A key built from a timestamp changes on every retry, which defeats the point and lets a retry send twice. Idempotency covers the replay window and conflict behavior. Full request shapes are in the API reference. 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 Loops 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 Loops’ new unsubscribes and bounces and import them into Brew as Subscribed: false. That stops marketing only. Transactional sends still reach these contacts, so don’t fire transactional triggers for addresses that hard-bounced or complained. Repeat before each type you move, until Loops 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 Loops first, so nobody gets it from both. Send to your most engaged contacts first and ramp over several days. Gradual send does the ramping for you. No history means no reputation, even on a verified domain.
  5. Move one type at a time. Start with your lowest-risk campaign, a newsletter, to confirm delivery and rendering. Then move workflows one at a time, once their triggers fire correctly. For each workflow, in order: pause it on Loops, publish the Brew automation, then point its signup forms or inbound integrations at Brew. Watch each in analytics before you move the next.
  6. Move transactional email last. Before you move any, add a separate transactional-purpose subdomain (see Transactional Email). A fire for a new address creates a subscribed contact, so only send marketing to people who opted in. This differs from Loops, where a transactional send creates no contact unless addToAudience is true. Resets and magic links 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 Loops as a fallback until every send type has a clean week in Brew, then close it down.
Never leave the same email type live in both places at once. That’s how a contact gets the same campaign twice.

From an Agent or Code

Steps 3 to 5 also run from an AI assistant connected over MCP. Ask in plain English, one step at a time:
“Import subscribed.csv, then unsubscribed.csv, as separate batches, and record the consent source as import.”
“Create an audience of subscribed contacts whose plan is pro, and tell me how many it holds.”
“Import this HTML as an editable email called Newsletter.”
Check each count against the old platform before you move on. From code:
  • Contacts. POST /v1/contacts/import-csv, or brew-cli contacts import-csv --file contacts.csv, once per file. Add Contacts covers limits and the consent record.
  • Segments. POST /v1/audiences with a name and filters, or brew-cli audiences create.
  • Templates. POST /v1/emails/import with the format and content. Import is free and uses no AI.
Step 6’s flows follow Build an Automation.

Quick Reference

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:

Search Documentation

Type in the “Ask any question” search bar at the top left to instantly find relevant documentation pages.

Ask ChatGPT or Claude

Click here to open the Copy page menu at the top of this page, then send the page to ChatGPT, Claude, or your editor.