Migrate from Kit to Unitpost

Contacts, suppressions, tags, templates and more come over in one import.
What comes with you
To leave Kit (formerly ConvertKit), paste a Kit V4 API key into Unitpost's Migration Assistant. It imports subscribers with their state and custom fields, tags as segments, bounces and complaints as suppressions, drafts, your 25 most recent broadcasts and every sequence email as templates with Liquid converted, sending addresses and webhooks. Unconfirmed subscribers are left out. Kit segments arrive as names only, and visual automations, forms, landing pages and commerce stay at Kit.
| Data | Result | What happens |
|---|---|---|
| Contacts | Imports | Active, cancelled, bounced and complained subscribers import with first_name and their custom fields. Kit has no last name field, so a custom field keyed last_name (or labelled Last name) becomes the last name. |
| Unsubscribes and consent | Imports | Cancelled, bounced and complained subscribers import unsubscribed. Unconfirmed subscribers (inactive in Kit's API) never confirmed double opt-in, so Kit can't email them either, and they are left out. |
| Suppressions (bounces and complaints) | Imports | Bounced and complained subscribers also import as bounce and complaint suppressions. Kit keeps no separate suppression list; the subscriber's state is the record. |
| Tags | Imports | Each tag becomes a static segment with its members. |
| Templates | Partly imports | Draft broadcasts, your 25 most recent sent broadcasts and every sequence email import as code-mode drafts, with snippets inlined and exact Liquid placeholders rewritten. Kit doesn't share its email template layouts, so a broadcast on a Classic template arrives without that template's header and footer. |
| Sending domains | Partly imports | Kit has no domains API. Domains come from your Kit sending addresses and are recreated as verifying with new DKIM keys. |
| Webhooks | Partly imports | Webhook endpoints and legacy webhooks import switched off with a new signing secret. Subscriber events map to contact.*, email.bounced and email.complained; form, sequence, purchase and broadcast events are not mapped. Kit doesn't offer webhooks on every plan. |
| Segments | Rebuild | Kit segments are saved filters, and Kit's API returns only their names, with no members and no filter. The preview lists them; rebuild each one as a dynamic segment. |
| Topics | Rebuild | Kit has no topics or subscription preferences. Create topics here if you want per-category opt-outs. |
| API keys | Rebuild | Kit keys stay at Kit. Create Unitpost API keys instead, and delete the Kit key you used for the import. |
| Send history and analytics | Stays behind | Broadcast stats, subscriber activity and sent broadcasts older than the 25 most recent stay at Kit. |
| Automations | Stays behind | Visual automations and rules aren't readable through Kit's API. Sequences are listed in the preview and their emails come over as templates; rebuild the order and delays in Unitpost Automations, which are in early access, or send from your code. |
| Forms, landing pages and commerce | Stays behind | Forms, landing pages, products, paid newsletters, purchases and recommendations stay at Kit. Unitpost has no hosted forms and doesn't bill readers. |
Before you start
- A Kit V4 API key: Settings → Developer → Add a new key, under V4 API Keys. V3 keys (
api_keyandapi_secret) don't work. - Kit's docs describe no read-only API key. The importer only reads; delete the key in Kit when the move is done.
- If Kit refuses a key you just made, check Billing in Kit: Kit answers the same way for a bad key and for an account whose plan or trial doesn't include API access right now.
- 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
Kit keeps sending until the last step, so there is no gap in delivery.
Run the Migration Assistant
Paste the V4 key in Settings → Migration. The preview lists the unconfirmed subscribers it leaves out, your Kit segments and sequences, and the templates whose Liquid needs review. Suppressions are written before contacts, so bounced and complained addresses land unsubscribed.
Verify your domain
Kit's API shares sending addresses, not DNS records. Open Domains and publish the records shown: MX and SPF on
unitpost.<your domain>and a DKIM TXT under_domainkey. They don't replace Kit's records, so both stay authenticated. Keep your single_dmarcrecord.Review Liquid and publish
{{ subscriber.first_name }}is now{{first_name}}and{{ unsubscribe_url }}is{{unsubscribe_url}}; custom fields map by key, and filters such as| default:are dropped. Unitpost fills flat{{name}}placeholders only, so{% if %}blocks and Kit-only tags such as{{ address }}stay as written for you to rewrite. Publish withPATCH /api/v1/email/templates/{id}.Rebuild segments as filters
Recreate each Kit segment in Segments as a dynamic segment on contact fields. Your tags already came over as static segments with their members.
Rebuild sequences
Each sequence email is a template named after its sequence and position. Rebuild the order and delays in Automations if your workspace has early access; until then, send them from your code on the same schedule.
Point signups at Unitpost
Kit forms and landing pages keep adding subscribers to Kit. Send signups from your own form with
POST /api/v1/contactsand add the contact to a segment where you used a tag. Kit's create call updates an existing subscriber; here an existing contact answers409, andPATCH /api/v1/contacts/{id or email}updates it.signup.ts// Before: Kit const kit = { "X-Kit-Api-Key": process.env.KIT_API_KEY, "content-type": "application/json" }; await fetch("https://api.kit.com/v4/subscribers", { method: "POST", headers: kit, body: JSON.stringify({ email_address: "user@example.com", first_name: "Ada" }), }); await fetch("https://api.kit.com/v4/tags/" + TAG_ID + "/subscribers", { method: "POST", headers: kit, body: JSON.stringify({ email_address: "user@example.com" }), }); // 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. Kit'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" });Re-key and enable webhooks
Kit signs deliveries with
X-Kit-Signature. Unitpost signs each body with awhsec_secret insvix-id,svix-timestampandsvix-signature. Rotate the secret in Webhooks to see it, update your handler, then setstatustoenabled. Unitpost also sends per-message events Kit doesn't, such asemail.deliveredandemail.opened.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 broadcast from Unitpost as a campaign to a small segment first, then to everyone, and delete the Kit key.
Name changes
Same ideas, new names
| Kit | Unitpost | Note |
|---|---|---|
| Subscriber | Contact | |
| Tag | Segment (static) | |
| Segment | Segment (dynamic) | Rebuilt by hand. |
| Custom field | Contact field | |
| Cancelled subscriber | Unsubscribed contact | |
| Unconfirmed subscriber | Not imported | |
| Broadcast | Campaign | |
| Sequence | Automations | In early access. |
| Visual automation | Automations | In early access. |
| Snippet | No equivalent | Inlined into templates on import. |
| Email template (layout) | Template | Layouts aren't readable; rebuild the design. |
{{ subscriber.first_name }} | {{first_name}} |
API calls
| Kit API | Unitpost API | Note |
|---|---|---|
| POST /v4/subscribers | POST /api/v1/contacts | Kit's call upserts; here an existing contact answers 409 and you update it with PATCH. |
| GET /v4/subscribers | GET /api/v1/contacts | |
| PUT /v4/subscribers/{id} | PATCH /api/v1/contacts/{id or email} | |
| GET /v4/tags | GET /api/v1/segments | |
| POST /v4/tags/{tag_id}/subscribers | POST /api/v1/segments/{id}/contacts | |
| GET /v4/custom_fields | GET /api/v1/contact-fields | |
| POST /v4/broadcasts | POST /api/v1/email/campaigns | |
| GET /v4/sequences | Automations | In early access. |
| POST /v4/webhook_endpoints | POST /api/v1/webhooks | |
X-Kit-Api-Key header | Authorization: Bearer <api key> | |
| No transactional API | POST /api/v1/email | Receipts and resets go to the same contacts as campaigns. |
Webhook events
| Kit event | Unitpost event | Note |
|---|---|---|
| subscriber.created | contact.created | |
| subscriber.activated, subscriber.unsubscribed | contact.updated | |
| subscriber.custom_field_value_updated | contact.updated | |
| subscriber.tag_added, subscriber.tag_removed | segment.joined, segment.left | The importer maps them to contact.updated; the segment events are the closer match. |
| subscriber.bounced | email.bounced | |
| subscriber.complained | email.complained | |
| subscriber.subscribed_to_form, subscriber.added_to_sequence, subscriber.sequence_completed | No equivalent | Not mapped by the importer. |
| broadcast.sent | email.sent | Ours fires per recipient. Not mapped by the importer. |
| Legacy: subscriber_activate, subscriber_unsubscribe, subscriber_bounce, subscriber_complain | contact.updated, email.bounced, email.complained | Legacy webhooks map the same way as the current ones. |
Before you cancel Kit
- Export subscribers as CSV, including unconfirmed ones, which the importer leaves out.
- Note who is in each Kit segment if you'll rebuild it; Kit's API can't list segment members.
- Write down each visual automation and rule, with its trigger and timing.
- Move or close your products and paid newsletters; Unitpost doesn't bill readers.
- Delete Kit's DNS records once no mail goes through Kit. Keep
_dmarcand Unitpost's records. - Delete the Kit API key you used for the import.
Facts about Kit checked 2026-11-05. Providers change their APIs and plans; check their docs before you rely on a detail.
Questions about leaving Kit
Why are my Kit segments empty?
Kit rejected my key. Why?
Do my sequences come over?
Can Unitpost sell my products or paid newsletter?
Leaving Kit?
Preview the migration on the free tier
Verify a domain, paste an API key, and see what would come across before anything is written.
