Skip to main content
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 and workflows get built separately in the dashboard. Both halves move. Campaigns and workflows become Emails and Automations, and a Loops transactional email becomes a trigger plus a one-step automation sending from a transactional-purpose domain, which skips the unsubscribe footer and delivers to unsubscribed contacts. Step 7 covers the code swap for both kinds of call. It 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

1. Export Contacts From Loops

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.
Audience page → filter to a segment if you want a subset → Export. 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 filter by Subscribed first if you only want active contacts. 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.

2. Map Your CSV

Notes:
  • userGroup and any custom properties become Brew custom properties. Bring userId too if anything downstream matches on it
  • These names are reserved and can’t be used for custom properties: email, first_name, last_name, subscribed, created_at, updated_at, validation_status, last_validated_at, import_id
  • 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
Personalization. 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, written the same way: {{ orderId }}. 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 }} if you prefer the namespaced form. One difference worth knowing: in Loops, a missing property value with no fallback means the email doesn’t send. In Brew the fallback lives inside the tag itself. There’s no separate step where you could forget one and have a send fail silently.

3. Import Into Brew

Audience → Add contacts → CSV Upload. Map columns, tick validation if the list hasn’t been checked recently, review, then import. Full import behavior is in Add Contacts.

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. Brew has one subscription state per contact, subscribed or unsubscribed. There are no opt-in categories and no subscriber-facing preference center. Marketing emails and automations respect that flag. Rebuild each list as a custom property plus an Audience filtered on it. You keep the targeting, but membership is yours to manage. Contacts can’t pick lists for themselves, and unsubscribing opts them out of all marketing email rather than one list. If a Loops list carried a real consent distinction, decide how to honor it before your first send.

5. Rebuild Templates

Open Emails in the sidebar, then use the Import tools on the right side of the canvas. See Importing existing designs. Loops themes, the shared styling across transactional 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.

6. Rebuild Workflows as Automations

Loops has a trick for re-running an existing contact through a workflow. Download a CSV, then re-upload it with “Trigger workflows” toggled 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 Your Code Over to Triggers

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 trial-ending 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 whose Send Email node uses a transactional-purpose sending domain, which is what skips the unsubscribe footer and delivers to unsubscribed contacts. transactionalId maps to the triggerEventId you fire, and 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:
  1. POST /v1/automations/triggers to create the trigger event, for example “Trial started”. Capture the triggerEventId. You can also create it in-app from Trigger events.
  2. POST /v1/emails to generate the email design. Capture both the emailId and the emailVersionId.
  3. GET /v1/domains for a verified domainId.
  4. POST /v1/automations with a Send Email node wired to that trigger. Each sendEmail node carries emailId, emailVersionId, domainId, subject, and previewText.
  5. PATCH /v1/automations/{automationId} with { "published": true }.
  6. Replace the Loops sendEvent call in your backend with a fire request.
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 is the shape of the migration, not a copy-paste replacement for it.

8. Cut Over

Move one email type at a time.
  1. Warm up your Brew sending domain before any real volume, most engaged contacts first, ramped over several days. Gradual send does the ramping for you.
  2. Import contacts and rebuild templates and automations while Loops keeps sending everything as normal.
  3. Cut over your lowest-risk campaign first, a newsletter, to confirm delivery and rendering.
  4. Move workflows one at a time, once their triggers are confirmed firing correctly. Watch each one in analytics before moving the next.
  5. Point signup forms and inbound integrations at Brew once everything upstream is verified.
  6. Move transactional email last. Resets and magic links are the highest-stakes sends, so cut them over one at a time, test-running each automation with a real payload before pointing production code at its trigger. Close Loops down once every send type has a clean week in Brew.
Never leave the same email type live in both places at once. That’s how a contact gets the same campaign twice.

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.

ChatGPT/Claude Integration

Click “Open in ChatGPT” at the top right of any page to explore it further with ChatGPT or Claude.