Migrate from Brevo to Unitpost
Easy migrationContacts with their email blocklist state, transactional blocked contacts and transactional templates all import; what you rebuild sits outside those lists (segments, SMS consent, automations).
What comes with you
To leave Brevo, paste a Brevo API key into Unitpost's Migration Assistant. It imports every contact with its attributes and email blocklist state, lists as static segments, transactional blocked contacts as suppressions, transactional templates as drafts, both webhook types switched off, and domains where Brevo's API exposes them. Segments, SMS consent, automations, campaign history, CRM and landing pages stay at Brevo, and `{{ params.x }}` placeholders need rewriting.
| Data | Result | What happens |
|---|---|---|
| Contacts | Imports | Every contact imports with FIRSTNAME and LASTNAME as its name and other attributes as custom fields; address objects and multiple-choice values are flattened into separate fields. |
| Unsubscribes and consent | Imports | A contact Brevo has blocklisted for email (emailBlacklisted) imports unsubscribed, which stops marketing here. Transactional contacts that unsubscribed import as unsubscribe suppressions. |
| Suppressions (bounces and complaints) | Imports | Transactional blocked contacts import with their reason: hard bounces, spam complaints and admin blocks. Admin blocks stop every send here, transactional included. |
| Lists and segments | Partly imports | Lists become static segments with their members, and list folders are flattened. Brevo segments are not read; rebuild them as dynamic segments. |
| Templates | Partly imports | Transactional templates import subject and HTML as drafts in code mode; text versions are not read. {{ params.x }} and {{ contact.X }} stay as written, and the preview warns about them. |
| Sending domains | Partly imports | Domains and their sender addresses are recreated as verifying with new DKIM keys when Brevo's domains API answers for your account; when it does not, the preview says so and you add them by hand. |
| Webhooks | Partly imports | Transactional and marketing webhooks import switched off with a new signing secret. request, uniqueOpened and listAddition are not mapped and are listed in the preview. |
| Topics | Rebuild | Brevo has no topics. Create them here if you want per-category opt-outs on campaigns. |
| API keys | Rebuild | API keys and SMTP keys stay at Brevo. Create Unitpost API keys instead. |
| SMS contacts and consent | Rebuild | The SMS attribute arrives as a text custom field, not as the contact's SMS number, and smsBlacklisted is not read. Re-record SMS consent before texting anyone. |
| Email campaigns | Rebuild | Not read. Recreate drafts and scheduled campaigns as Unitpost campaigns on a segment. |
| Send history and analytics | Stays behind | Email logs, campaign reports and statistics stay at Brevo. |
| Automations | Stays behind | Brevo automations stay; Unitpost automations are not launched yet. |
| CRM, landing pages, chat and WhatsApp | Stays behind | Unitpost has no equivalent; keep Brevo for them if you use them. |
Before you start
- A Brevo API key (
xkeysib-…) from Settings → SMTP & API. Keys created with the MCP option work too. - If Brevo blocks unknown IPs for API keys (Security → Authorised IPs), choose Deactivate for API keys before connecting, or Brevo rejects even a valid key.
- A Unitpost API key with
emails:send, 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.
- If you text contacts: SMS on Unitpost is on paid plans only, and marketing texts need consent recorded per contact.
The cut-over plan
Brevo keeps sending until the last step, so there is no gap in delivery.
Run the Migration Assistant
Paste the key in Settings → Migration. Suppressions are written before contacts, so blocked and blocklisted addresses land unsubscribed. Check the domains line in the preview: if Brevo's domains API is not enabled for your account, add the domains by hand in step 2.
Verify your domains next to Brevo
Open Domains (add any the importer could not read) and publish the records shown: MX and SPF on
unitpost.<your domain>and a DKIM TXT under_domainkey. They do not replace Brevo's records, so both providers stay authenticated. Keep your single_dmarcrecord.Rewrite template variables and publish
{{ params.order_id }}becomes{{order_id}}, filled fromtemplate.variables.{{ contact.FIRSTNAME }}and{{ contact.LASTNAME }}become{{first_name}}and{{last_name}}, which fill from the recipient's contact. Set receipts totransactional(imports land as marketing, which adds a footer) and publish withPATCH /api/v1/email/templates/{id}.Switch the API call
senderbecomes afromstring,totakes addresses,htmlContentbecomeshtml, andtemplateIdwithparamsbecomestemplate: { id, variables }. Theapi-keyheader becomesAuthorization: Bearer, and sends accept anIdempotency-Key.send.ts// Before: Brevo await fetch("https://api.brevo.com/v3/smtp/email", { method: "POST", headers: { "api-key": process.env.BREVO_API_KEY, "content-type": "application/json" }, body: JSON.stringify({ sender: { name: "Acme", email: "hi@acme.com" }, to: [{ email: "user@example.com" }], templateId: 8, params: { order_id: "A-1009" }, }), }); // After: Unitpost import { Unitpost } from "unitpost"; const unitpost = new Unitpost(process.env.UNITPOST_API_KEY); const { data, error } = await unitpost.email.send({ from: "Acme <hi@acme.com>", to: "user@example.com", template: { id: "tmpl_123", variables: { order_id: "A-1009" } }, });Re-key and enable webhooks
Brevo protects webhooks with credentials in the URL, a bearer token or an IP allowlist; Unitpost signs each body with a
whsec_secret in the svix headers (svix-id,svix-timestamp,svix-signature). Rotate the secret in Webhooks to see it, add verification, addrequestevents by hand if you used them, then setstatustoenabled.Re-record SMS consent
Brevo's SMS opt-outs do not import. Before any marketing text, record each contact's consent where they gave it (
POST /api/v1/contacts/{id}/sms-consent) or ask them to confirm withPOST /api/v1/contacts/{id}/sms-opt-in-request. A phone number on a contact is not consent.Send tests
Send each template with
POST /api/v1/email/templates/{id}/test-send, and firePOST /api/v1/webhooks/{id}/testat each endpoint.Move traffic in stages
Move transactional mail first, watching
email.bouncedandemail.complained, then campaigns. Brevo stays authenticated, so rolling back is a config change.Cancel Brevo last
Finish the checks below, then downgrade or close the account.
Name changes
Same ideas, new names
| Brevo | Unitpost | Note |
|---|---|---|
| Contact list | Segment (static) | |
| Segment | Segment (dynamic) | Rebuilt by hand. |
| List folder | No equivalent | Flattened on import. |
| Contact attribute | Contact field | |
Email blocklisted (emailBlacklisted) | Unsubscribed contact | |
| Transactional blocked contact | Suppression | |
params | Template variables | |
| Email campaign | Campaign | |
| Automation | Automations | Not launched yet. |
API calls
| Brevo API | Unitpost API | Note |
|---|---|---|
| POST /v3/smtp/email | POST /api/v1/email | |
templateId + params | template: { id, variables } | |
api-key header | Authorization: Bearer <api key> | |
| POST /v3/contacts | POST /api/v1/contacts | |
| PUT /v3/contacts/{identifier} | PATCH /api/v1/contacts/{id or email} | |
| POST /v3/contacts/import | POST /api/v1/contacts/imports | Up to 5,000 rows per request. |
| GET /v3/contacts/segments | GET /api/v1/segments | |
| GET /v3/smtp/blockedContacts | GET /api/v1/suppressions | |
| POST /v3/emailCampaigns | POST /api/v1/email/campaigns | |
| POST /v3/webhooks | POST /api/v1/webhooks |
SMTP settings
| Brevo SMTP | Unitpost SMTP | Note |
|---|---|---|
| smtp-relay.brevo.com | smtp.unitpost.com | |
| Login: the SMTP login from SMTP & API settings | Username unitpost | |
| Password: an SMTP key (not the API key) | Password: a Unitpost API key with emails:send | |
| 587, 2525 (encryption optional) | 587, 2587, 25 (STARTTLS) | TLS 1.2 or later is required before login. |
| 465 (SSL or TLS) | 465, 2465 (implicit TLS) |
Webhook events
| Brevo event | Unitpost event | Note |
|---|---|---|
| sent | email.sent | |
| request | email.sent | Brevo's other name for sent; not mapped by the importer. |
| delivered | email.delivered | |
| softBounce, deferred | email.delivery_delayed | |
| hardBounce | email.bounced | |
| spam | email.complained | |
| opened | email.opened | |
| uniqueOpened | email.opened | Not mapped by the importer. |
| click | email.clicked | |
| blocked, invalid | email.failed | A send our suppression list stops is email.suppressed. |
| unsubscribed, contactUpdated | contact.updated | |
| contactDeleted | contact.deleted | |
| listAddition | segment.joined | Not mapped; bulk imports do not fire it. |
Before you cancel Brevo
- Export contacts and segments as files, including SMS numbers and SMS opt-outs, which do not import.
- Export the campaign reports and email logs you want to keep; none of them move.
- Pull the transactional blocked contacts one last time and add new ones here.
- Delete Brevo's DKIM and verification records once no mail goes through Brevo. Keep
_dmarcand Unitpost's records. - Turn the IP allowlist back on if you switched it off for the migration, then revoke the API key you used.
Facts about Brevo checked 2026-11-02. Providers change their APIs and plans; check their docs before you rely on a detail.
Questions about leaving Brevo
Brevo's free plan sends more. Why switch?
Do my Brevo automations come over?
Will my SMS opt-outs come with me?
Brevo rejected the connection with a valid key. Why?
Can Unitpost replace Brevo entirely?
Leaving Brevo?
Preview the migration on the free tier
Verify a domain, paste a read-only key, and see what would come across before anything is written.