Migrate from Klaviyo to Unitpost

Contacts, suppressions, lists, segments and more come over in one import.
What comes with you
To leave Klaviyo, paste a Klaviyo Read-Only private key into Unitpost's Migration Assistant. It imports profiles with their email consent and dated SMS consent, custom properties and location, lists and segments as today's members, bounces and manual suppressions, saved templates, drafts and your 25 most recent campaigns with Klaviyo tags converted, and your sending domain. Profiles with no email are left out. Flows, segment filters, webhooks, forms and analytics stay at Klaviyo.
| Data | Result | What happens |
|---|---|---|
| Contacts | Imports | Every profile with an email address imports with first and last name, custom properties, organization, title, locale and location (IP address excluded). Profiles with only a phone number, or no identifier at all, are left out: a contact here needs an email. |
| Unsubscribes and consent | Imports | Subscribed profiles import subscribed, with the date and method Klaviyo recorded kept as contact fields. Unsubscribed profiles import unsubscribed. Profiles that never subscribed (often checkout-only customers, whom Klaviyo can email on implied consent) import unsubscribed from marketing: campaigns skip them, transactional email still reaches them. |
| Suppressions (bounces and blocks) | Imports | Hard bounces, invalid addresses, spam complaints and manual suppressions on a profile import as suppressions. Klaviyo shows many spam complaints only as an unsubscribe, so those arrive unsubscribed rather than blocked; to block them for every kind of email, export Klaviyo's suppressed profiles and import that file too. |
| SMS numbers and consent | Imports | A profile's phone number becomes the contact's SMS number. An SMS opt-in imports only with the date Klaviyo recorded for it, and an SMS opt-out always carries over. Klaviyo requires express SMS consent, so most opt-ins arrive with their date. |
| Lists | Imports | Each list becomes a static segment with its members. Someone who unsubscribed from one list only is left out of that segment. |
| Segments | Partly imports | Active segments come over with today's members as fixed segments; their filters use Klaviyo's own events and properties, so rebuild the filter here if a segment should keep updating. Inactive segments are listed, and very large segments may come as names only. |
| Templates | Partly imports | Saved templates, draft campaigns and your 25 most recent sent email campaigns import as code-mode drafts, with exact Klaviyo tags rewritten. Klaviyo may return no body for drag-and-drop templates; those arrive empty and are listed, so export their HTML from Klaviyo. |
| Sending domains | Partly imports | Klaviyo's sending-domain API is beta-only. The domain of your default sender address is recreated here as verifying, with new DKIM keys. |
| Topics | Rebuild | Klaviyo has no subscription topics in its API. Create topics here if you want per-category opt-outs. |
| Webhooks | Rebuild | Klaviyo's API truncates webhook URLs and never returns their secrets, so the preview names your webhooks and their topics for you to add here. Only some Klaviyo plans have webhooks. |
| API keys | Rebuild | Klaviyo keys stay at Klaviyo. Create Unitpost API keys instead, and delete the Klaviyo key you used for the import. |
| Flows | Stays behind | Flows don't move. The preview lists each one with its trigger and status; rebuild them in Unitpost Automations, which are in early access, or send from your code when the event happens. |
| Send history, analytics and forms | Stays behind | Campaign and flow stats, events, predictive analytics, sign-up forms and older sent campaigns stay at Klaviyo. Unitpost has no hosted forms or store integrations. |
Before you start
- A Klaviyo private API key with Read-Only access: Settings → API keys → Create Private API Key, then Read-Only Key. Owners, Admins and Managers can create one. The six-character public key (site ID) can't read your data.
- Klaviyo shows a private key once; copy it before closing the dialog.
- If you text contacts: SMS on Unitpost is on paid plans only, and marketing texts need consent on record per contact.
- A Unitpost API key for your app with
emails:send, pluscontacts:writeandsegments:writeif your signup form adds contacts, created in API keys. - A second Unitpost key for the migration steps, with
templates:writeandwebhooks:manage, saved asUNITPOST_MIGRATION_KEY. Keep it out of your app and revoke it once the move is done. - Access to your DNS host.
The cut-over plan
Klaviyo keeps sending until the last step, so there is no gap in delivery.
Run the Migration Assistant
Paste the Read-Only key in Settings → Migration. The preview counts what comes over and lists what doesn't: profiles with no email, never-subscribed profiles, your flows, large or inactive segments, and templates whose tags need review. Suppressions are written before contacts, so bounced and blocked addresses land suppressed.
Verify your domain
Open Domains and publish the records shown: MX and SPF on
unitpost.<your domain>and a DKIM TXT under_domainkey. They don't replace Klaviyo's records, so both stay authenticated while you switch. Keep your single_dmarcrecord.Review tags and publish
{{ first_name }}is now{{first_name}},{{ person|lookup:'Favorite Color' }}becomes the imported field{{favorite_color}}, and{% unsubscribe %}becomes a link to{{unsubscribe_url}}. Filters such as|default:'friend'are dropped. Unitpost fills flat{{name}}placeholders only, so{% if %}and{% for %}blocks, event data and tags such as{% web_view %}or{{ organization.name }}stay as written for you to rewrite. Publish withPATCH /api/v1/email/templates/{id}.Rebuild segments and flows
Recreate the segments that should keep updating in Segments as dynamic segments on contact fields. Rebuild each flow in Automations if your workspace has early access; until then, send from your code when the event happens, with
POST /api/v1/email.Point signups and events at Unitpost
Klaviyo forms keep adding profiles to Klaviyo. Send signups from your own form with
POST /api/v1/contactsand add the contact to a segment where you used a list. Klaviyo's profile import upserts; here an existing contact answers409, andPATCH /api/v1/contacts/{id or email}updates it. Events you sent to Klaviyo to start flows become direct sends.signup.ts// Before: Klaviyo const klaviyo = { Authorization: "Klaviyo-API-Key " + process.env.KLAVIYO_PRIVATE_KEY, revision: "2026-10-15", "content-type": "application/vnd.api+json", }; const res = await fetch("https://a.klaviyo.com/api/profile-import", { method: "POST", headers: klaviyo, body: JSON.stringify({ data: { type: "profile", attributes: { email: "user@example.com", first_name: "Ada" } } }), }); const { data: profile } = await res.json(); await fetch("https://a.klaviyo.com/api/lists/" + LIST_ID + "/relationships/profiles", { method: "POST", headers: klaviyo, body: JSON.stringify({ data: [{ type: "profile", id: profile.id }] }), }); // After: Unitpost import { Unitpost } from "unitpost"; const unitpost = new Unitpost(process.env.UNITPOST_API_KEY); const { error } = await unitpost.contacts.create({ email: "user@example.com", first_name: "Ada" }); // 409 conflict: the contact already exists. Klaviyo's call upserts; update with contacts.update. if (error && error.code !== "conflict") throw new Error(error.message); await unitpost.segments.addMember("seg_123", { contact: "user@example.com" });Check SMS consent before texting
Numbers whose opt-in had no date arrived without consent. Record consent where the contact gave it (
POST /api/v1/contacts/{id}/sms-consent) or ask them to confirm withPOST /api/v1/contacts/{id}/sms-opt-in-request. Klaviyo SMS opt-outs came over and stay in force.Add webhooks
Add each endpoint the preview listed in Webhooks. Klaviyo signs deliveries with
Klaviyo-Signature; Unitpost signs each body with awhsec_secret insvix-id,svix-timestampandsvix-signature, so update your handler.Test, move traffic, then delete the key
Send each template with
POST /api/v1/email/templates/{id}/test-sendand firePOST /api/v1/webhooks/{id}/testat each endpoint. Send your next campaign from Unitpost to a small segment first, then to everyone. Turn each Klaviyo flow off once its replacement runs here, then delete the Klaviyo key.
Name changes
Same ideas, new names
| Klaviyo | Unitpost | Note |
|---|---|---|
| Profile | Contact | |
| List | Segment (static) | |
| Segment | Segment (dynamic) | Members come over; rebuild the filter. |
| Custom property | Contact field | |
| Never subscribed | Unsubscribed from marketing | Transactional email still reaches them. |
| Suppressed profile | Suppression | |
| Campaign | Campaign | |
| Flow | Automations | In early access. |
| Metric (event) | No launched equivalent | Send from your code when the event happens. |
{{ first_name }} | {{first_name}} | |
{% unsubscribe %} | A link to {{unsubscribe_url}} |
API calls
| Klaviyo API | Unitpost API | Note |
|---|---|---|
| POST /api/profile-import | POST /api/v1/contacts | Klaviyo's call upserts; here an existing contact answers 409 and you update it with PATCH. |
| GET /api/profiles | GET /api/v1/contacts | |
| PATCH /api/profiles/{id} | PATCH /api/v1/contacts/{id or email} | |
| GET /api/lists | GET /api/v1/segments | |
| POST /api/lists/{id}/relationships/profiles | POST /api/v1/segments/{id}/contacts | |
| POST /api/profile-subscription-bulk-create-jobs | POST /api/v1/contacts/{id}/sms-consent | Email consent is the contact's unsubscribed flag; SMS consent is recorded per contact. |
| POST /api/profile-suppression-bulk-create-jobs | POST /api/v1/suppressions | |
| POST /api/events | No launched equivalent | Events start Klaviyo flows. Send the email from the code that fires the event, with POST /api/v1/email. |
| POST /api/campaigns | POST /api/v1/email/campaigns | |
| POST /api/webhooks | POST /api/v1/webhooks | |
Authorization: Klaviyo-API-Key … plus a revision header | Authorization: Bearer <api key> |
Webhook events
| Klaviyo event | Unitpost event | Note |
|---|---|---|
| event:klaviyo.received_email | email.delivered | |
| event:klaviyo.opened_email | email.opened | |
| event:klaviyo.clicked_email | email.clicked | |
| event:klaviyo.bounced_email | email.bounced | |
| event:klaviyo.marked_email_as_spam | email.complained | |
| event:klaviyo.dropped_email | email.suppressed | |
| event:klaviyo.subscribed_to_email_marketing, event:klaviyo.unsubscribed_from_email_marketing | contact.updated | |
| event:klaviyo.sent_sms | sms.sent | |
| event:klaviyo.received_sms | sms.received | |
| event:klaviyo.failed_to_deliver_sms | sms.failed | |
| Every topic | Add the endpoint by hand | Klaviyo's API truncates webhook URLs, so the importer lists your webhooks instead of copying them. |
Before you cancel Klaviyo
- Export the profiles with no email address if you need them; the importer leaves them out.
- Export your suppressed profiles (Audience → Profiles → View suppressed profiles) and import them as suppressions, so complaints shown as unsubscribes stay blocked.
- Write down each flow with its trigger, filters and timing.
- Note the filters of the segments you'll rebuild; only their members came over.
- Delete Klaviyo's DNS records once no mail goes through Klaviyo. Keep
_dmarcand Unitpost's records. - Delete the Klaviyo API key you used for the import.
Facts about Klaviyo checked 2026-11-06. Providers change their APIs and plans; check their docs before you rely on a detail.
Questions about leaving Klaviyo
Why are some profiles missing?
Why did customers who never subscribed come over unsubscribed?
Do my SMS subscribers come over?
Do my flows come over?
Leaving Klaviyo?
Preview the migration on the free tier
Verify a domain, paste an API key, and see what would come across before anything is written.
