Migrate from Loops

Loops and Plunk work the same way: contacts with properties, events that start workflows, and transactional emails sent from templates. Moving means importing the contact CSV, recreating emails, and pointing your event and send calls at Plunk.

Updated · 8 min read

What moves, and what you rebuild

From LoopsIn PlunkHow
Sending domainDomainNew DNS records, alongside Loops’
Contacts and propertiesContacts and custom fieldsCSV import, subscription status included
Mailing listsSegmentsA column per list, then a segment
Transactional emailsTemplatesRecreate the design, swap tags for Liquid
EventsTracked eventsChange the endpoint
Workflows (formerly loops)WorkflowsRebuild by hand
WebhooksWorkflows with a webhook stepRebuild by hand

1. Verify your domain in Plunk

Add your sending domain under Settings → Domains in Plunk. Plunk shows three CNAME records named <token>._domainkey, pointing to <token>.dkim.amazonses.com. Add them at your DNS provider and Plunk marks the domain verified once they resolve.

You do not need to remove anything first. Loops puts its SPF and MX records on an envelope. subdomain of your sending domain. Plunk’s DKIM selectors are different, so both sets of records can live side by side and Loops keeps working while you move.

Plunk also offers an optional custom MAIL FROM domain: an MX and an SPF TXT record on a plunk. subdomain. It aligns bounces with your domain and is worth adding, but sending works without it.

2. Move contacts

In Loops, open your audience, use the ••• menu next to the filter box and choose Download. The CSV has one column per contact property, including email and subscribed, which Plunk reads as they are. No renaming is needed for those two.

Custom properties come along as custom fields, lowercased: firstName becomes firstname and userId becomes userid. Templates and segments then refer to the lowercase names.

ColumnWhat Plunk does with it
emailRequired. Rows without a valid address are skipped and listed as errors.
subscribedOptional. true, 1 or yes subscribes the contact; any other value unsubscribes it. Empty keeps an existing contact as it is; new contacts default to subscribed.
Anything elseStored as a custom field. Headers are lowercased, so firstName becomes firstname and First Name becomes first name.

3. Recreate your emails

Loops can export an email’s content only as LMX, its own markup, through GET /api/v1/email-messages/{id}. There is no HTML export, so recreate each design in Plunk’s editor and change the tags:

LoopsPlunk
{firstName} or {contact.firstName}{{firstname}}
{DATA_VARIABLE:orderId} or {data.orderId}{{orderId}}
{EVENT_PROPERTY:plan} or {event.plan}{{plan}}, once the event saves it on the contact

In Loops, a transactional email’s subject, sender and reply-to live in the email itself. Plunk templates hold them too, as defaults the send request can override.

4. Change your API calls

Transactional sends map almost field for field:

Loops /api/v1/transactionalPlunk /v1/sendNotes
transactionalIdtemplateThe Plunk template’s ID
emailtoA string, {name, email} or an array
dataVariablesdata
addToAudiencesubscribedEvery recipient becomes a Plunk contact; subscribed: true also opts them in
attachments[] {filename, contentType, data}attachments[] {filename, contentType, content}Enabled for everyone, no support request
Idempotency-Key headerIdempotency-Key headerKept for 24 hours
Before: Loops
await fetch('https://app.loops.so/api/v1/transactional', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${process.env.LOOPS_API_KEY}`,
  },
  body: JSON.stringify({
    transactionalId: 'clfq6dinn000yl70fgwwyp82l',
    email: 'ana@acme.com',
    dataVariables: {orderId: '3980'},
  }),
});
After: Plunk
const response = await fetch('https://send-api.akmarketingseo.com/v1/send', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${process.env.PLUNK_SECRET_KEY}`,
  },
  body: JSON.stringify({
    template: 'YOUR_PLUNK_TEMPLATE_ID',
    to: 'ana@acme.com',
    data: {orderId: '3980'},
  }),
});

if (!response.ok) {
  throw new Error(`Plunk returned ${response.status}`);
}

Events move the same way:

Loops /api/v1/events/sendPlunk /v1/track
emailemail
eventNameevent
eventProperties and root-level contact propertiesdata
mailingListsA field in data, plus a segment on it

Simple values in data are saved on the contact. Wrap a value as {value: …, persistent: false} to make it available to the workflow without storing it. The track endpoint accepts your public key, so it can run in the browser.

If you used Loops’ SMTP server, note that it expects a JSON body naming a transactional email. Plunk’s SMTP server takes ordinary email instead: host smtp.useplunk.com, port 587, username plunk and your secret key as the password.

5. Rebuild workflows

Loops returns a workflow’s structure from GET /api/v1/workflows/{id}, one node at a time, so it can serve as a checklist, but there is no import. Rebuild each one in Plunk: the trigger becomes an event, segment entry or schedule trigger, and the emails, delays and branches become steps.

6. Rebuild webhooks

Plunk has no global webhook setting. Instead, you create a workflow that starts on an event and add a Webhook step that posts to your URL. One workflow per event you care about, each with its own URL and headers if you want them.

Loops eventPlunk event
transactional.email.sent, campaign.email.sent, loop.email.sentemail.sent
email.deliveredemail.delivery
email.openedemail.open
email.clickedemail.click
email.hardBounced, email.softBouncedemail.bounce
email.spamReportedemail.complaint
contact.unsubscribed, email.unsubscribedcontact.unsubscribed
email.resubscribedcontact.subscribed

Plunk’s email.open and email.click fire on the first open and first click of each email, not on every one. The payloads differ, so the code that reads them needs updating. Plunk’s payload for each event is listed in the webhooks guide.

7. Switch over

  1. Send to yourself first

    Send a few real templates from Plunk to your own inboxes and check rendering, links and variables. Gmail’s “Show original” should report DKIM pass for your domain.
  2. Move one kind of email at a time

    Switch one flow, such as password resets, and watch delivery in Plunk for a day before moving the rest. Loops stays available as a fallback the whole time.
  3. Remove the old records last

    Once nothing sends through Loops any more, wait a few days for any retries to settle, then remove its DNS records and cancel the plan.

Keep reading

Frequently asked questions

Move from Loops at your own pace.

Start on the free plan with 1,000 emails a month, no credit card needed. When you need more, every email is $0.001, with unlimited contacts and every feature included.