Migrate from Kit to Unitpost

Kit tags importing as Unitpost segments with their members, and a Kit segment, which arrives as a name, rebuilt as a filter

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.

DataResultWhat happens
ContactsImportsActive, 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 consentImportsCancelled, 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)ImportsBounced and complained subscribers also import as bounce and complaint suppressions. Kit keeps no separate suppression list; the subscriber's state is the record.
TagsImportsEach tag becomes a static segment with its members.
TemplatesPartly importsDraft 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 domainsPartly importsKit has no domains API. Domains come from your Kit sending addresses and are recreated as verifying with new DKIM keys.
WebhooksPartly importsWebhook 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.
SegmentsRebuildKit 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.
TopicsRebuildKit has no topics or subscription preferences. Create topics here if you want per-category opt-outs.
API keysRebuildKit keys stay at Kit. Create Unitpost API keys instead, and delete the Kit key you used for the import.
Send history and analyticsStays behindBroadcast stats, subscriber activity and sent broadcasts older than the 25 most recent stay at Kit.
AutomationsStays behindVisual 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 commerceStays behindForms, 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_key and api_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, plus contacts:write and segments:write if your signup form adds contacts, created in API keys.
  • A second Unitpost key for the migration steps, with templates:write and webhooks:manage, saved as UNITPOST_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.

  1. 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.

  2. 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 _dmarc record.

  3. 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 with PATCH /api/v1/email/templates/{id}.

  4. 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.

  5. 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.

  6. Point signups at Unitpost

    Kit forms and landing pages keep adding subscribers to Kit. Send signups from your own form with POST /api/v1/contacts and add the contact to a segment where you used a tag. Kit's create call updates an existing subscriber; here an existing contact answers 409, and PATCH /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" });
  7. Re-key and enable webhooks

    Kit signs deliveries with X-Kit-Signature. Unitpost signs each body with a whsec_ secret in svix-id, svix-timestamp and svix-signature. Rotate the secret in Webhooks to see it, update your handler, then set status to enabled. Unitpost also sends per-message events Kit doesn't, such as email.delivered and email.opened.

  8. Test, move traffic, then delete the key

    Send each template with POST /api/v1/email/templates/{id}/test-send and fire POST /api/v1/webhooks/{id}/test at 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

KitUnitpostNote
SubscriberContact
TagSegment (static)
SegmentSegment (dynamic)Rebuilt by hand.
Custom fieldContact field
Cancelled subscriberUnsubscribed contact
Unconfirmed subscriberNot imported
BroadcastCampaign
SequenceAutomationsIn early access.
Visual automationAutomationsIn early access.
SnippetNo equivalentInlined into templates on import.
Email template (layout)TemplateLayouts aren't readable; rebuild the design.
{{ subscriber.first_name }}{{first_name}}

API calls

Kit APIUnitpost APINote
POST /v4/subscribersPOST /api/v1/contactsKit's call upserts; here an existing contact answers 409 and you update it with PATCH.
GET /v4/subscribersGET /api/v1/contacts
PUT /v4/subscribers/{id}PATCH /api/v1/contacts/{id or email}
GET /v4/tagsGET /api/v1/segments
POST /v4/tags/{tag_id}/subscribersPOST /api/v1/segments/{id}/contacts
GET /v4/custom_fieldsGET /api/v1/contact-fields
POST /v4/broadcastsPOST /api/v1/email/campaigns
GET /v4/sequencesAutomationsIn early access.
POST /v4/webhook_endpointsPOST /api/v1/webhooks
X-Kit-Api-Key headerAuthorization: Bearer <api key>
No transactional APIPOST /api/v1/emailReceipts and resets go to the same contacts as campaigns.

Webhook events

Kit eventUnitpost eventNote
subscriber.createdcontact.created
subscriber.activated, subscriber.unsubscribedcontact.updated
subscriber.custom_field_value_updatedcontact.updated
subscriber.tag_added, subscriber.tag_removedsegment.joined, segment.leftThe importer maps them to contact.updated; the segment events are the closer match.
subscriber.bouncedemail.bounced
subscriber.complainedemail.complained
subscriber.subscribed_to_form, subscriber.added_to_sequence, subscriber.sequence_completedNo equivalentNot mapped by the importer.
broadcast.sentemail.sentOurs fires per recipient. Not mapped by the importer.
Legacy: subscriber_activate, subscriber_unsubscribe, subscriber_bounce, subscriber_complaincontact.updated, email.bounced, email.complainedLegacy 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 _dmarc and 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's API returns a segment's name and nothing else: no members and no filter. The importer lists them so you can rebuild each as a dynamic segment. Tags are different: they came over as static segments with their members.

Kit rejected my key. Why?

Usually it's a V3 key. Kit's V3 `api_key` and `api_secret` don't work on its V4 API, which is the only one that shares segments, templates and webhooks. Create a key under V4 API Keys in Settings → Developer. Kit also refuses API calls when an account's plan or trial doesn't include API access at that moment, so check Billing too.

Do my sequences come over?

Their emails do, as templates named after the sequence and position. The order, delays and send days don't, because Unitpost has no sequence object. Rebuild them in Automations, which are in early access, or send from your code.

Can Unitpost sell my products or paid newsletter?

No. Unitpost doesn't bill readers. Keep products and paid subscriptions in Kit or your payment tool, and send to paying readers from a segment.

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.