Migrate from Brevo to Loops
This guide moves your transactional email and marketing contacts from the Brevo v3 API to Loops. You will swap the SDK, move your contacts and templates, map each API call to its Loops equivalent, and cut over without hurting deliverability.
Should you do this?
Brevo is a multichannel platform. It sends transactional email over an HTTP API and an SMTP relay, holds a marketing contacts database with lists and folders, stores templates, and also sends SMS and WhatsApp on top of a CRM and marketing-automation suite. Loops is email only.
Loops has no SMS, WhatsApp, CRM, or sales-pipeline features, so keep Brevo or another provider for those jobs. Loops supports transactional template-based SMTP at smtp.loops.so:587, but not arbitrary raw HTML or MIME relay. An app using smtp-relay.brevo.com can send Loops’ structured template payload over SMTP or move to the HTTP API.
Loops fits when your need is email in one place: transactional templates for account mail, plus campaigns, contacts, mailing lists, and event-triggered workflows for lifecycle and marketing. If you are consolidating those onto one platform, the migration is straightforward. Two structural changes to plan for. In Brevo you can send htmlContent at request time. In Loops the subject, body, and design live in a published template, and your code passes only dynamic values as dataVariables. Brevo also identifies contacts by email, internal id, or ext_id, where Loops uses email or userId. Copy edits move into the Loops editor instead of a deploy. The tradeoff is that you keep the dataVariables names in sync between the template and your code.
Swap the packages
Remove the Brevo SDK and install the Loops SDK. The package is named loops, not loops-so or loops-js.
The Loops SDK is server-side only. Browser calls to the Loops API are blocked by CORS by design, so keep your API key on the server.
API mapping
Map each Brevo call to its Loops equivalent. Where Loops has no equivalent, the row says so.
Transactional send. Brevo POST /v3/smtp/email (or apiInstance.sendTransacEmail) becomes Loops POST /v1/transactional (or loops.sendTransactionalEmail).
Authentication. Brevo sends the api-key: YOUR_API_KEY header; Loops uses Authorization: Bearer YOUR_API_KEY.
Base URL. Brevo https://api.brevo.com/v3 becomes Loops https://app.loops.so/api.
Templates and variables. Brevo stores a template referenced by a numeric templateId, personalized with a params object and {{ params.X }} syntax. Loops references a published template by transactionalId and passes values as dataVariables.
Contact upsert. Brevo POST /v3/contacts with updateEnabled: true (identified by email, id, or ext_id) becomes Loops loops.updateContact({ email, properties, mailingLists }), mapping to PUT /v1/contacts/update (identified by email or userId).
Custom fields. Brevo attributes, created with POST /v3/contacts/attributes/{category}/{name} and sent UPPERCASE, become Loops contact properties, created with POST /v1/contacts/properties ({ name, type }, where type is string, number, boolean, or date) and sent camelCase.
Lists. Brevo GET /v3/contacts/lists with numeric listIds on the contact becomes Loops GET /v1/lists for ids, then mailingLists: { [listId]: true }.
Suppression and unsubscribes. Brevo manages these per list and account. Loops exposes GET /v1/contacts/suppression and DELETE /v1/contacts/suppression, and auto-suppresses hard bounces and complaints.
Delivery events. Brevo sends webhooks for delivered, soft bounce, hard bounce, spam, opened, click, and unsubscribe. Loops webhooks expose email.delivered, email.softBounced, email.hardBounced, email.spamReported, email.opened, email.clicked, and email.unsubscribed.
SMTP submission. Brevo accepts conventional SMTP content. Loops accepts a structured transactional-template payload over SMTP at smtp.loops.so:587; use the HTTP API when that integration model fits better.
SMS and WhatsApp. Brevo sends transactional and marketing SMS plus WhatsApp. Loops has neither; keep Brevo or a dedicated provider for those channels.
Before and after
Contact upsert
Before, with the Brevo SDK:
After, with the Loops SDK:
Brevo attributes like FIRSTNAME and LASTNAME map to Loops firstName and lastName. Other UPPERCASE attributes become camelCase contact properties. loops.updateContact upserts by email or userId with no separate update flag.
Transactional send
Before, with the Brevo SDK:
After, with Loops:
The Brevo numeric templateId becomes a Loops transactionalId, and the params object becomes dataVariables. Keep the dataVariables keys identical to the variable names in the published Loops template, including case. A missing required variable returns 400.
Environment
Replace the Brevo variable with the Loops key and the ids you copy from the dashboard.
Loops has one base URL, https://app.loops.so/api. Authenticate with Authorization: Bearer YOUR_API_KEY, in place of the Brevo api-key header.
Dashboard setup and API-assisted migration
API key creation, sending-domain verification, mailing-list creation, and bulk CSV import still happen in the dashboard. Transactional templates and campaign drafts can be migrated with the Content API or CLI.
Create and test the API key. Generate a key under Settings -> API. Confirm it works with GET /v1/api-key, which returns { success: true, teamName }.
Verify your sending domain. Brevo domain authentication (its SPF, DKIM, and DMARC records) does not carry over. In Loops, add the SPF, DKIM, MX, and default DMARC records Loops shows during sending-domain setup, then verify from the domain settings page. DNS can take up to an hour to propagate, so do this first. Sends only work from a verified domain.
Migrate each Brevo template with the Content API or CLI. Create the transactional email, update its draft email message with LMX and matching case-sensitive variables, then publish it. Create campaign drafts with POST /v1/campaigns and update their email messages the same way.
Recreate custom fields and lists. Recreate the attributes you use as Loops contact properties with POST /v1/contacts/properties, and recreate your lists as Loops mailing lists, then read their ids with GET /v1/lists.
For contacts, there is no public bulk-contact-import API. Bulk import is done in the dashboard under Audience -> import CSV. For a programmatic move, loop over loops.updateContact per contact, staying within the rate limit.
Cutover checklist
Carry suppression and consent over from day one. Export your Brevo unsubscribed and blocklisted contacts and carry that state into Loops. Set subscribed: false on those contacts, or use the suppression endpoints (GET /v1/contacts/suppression to inspect, DELETE /v1/contacts/suppression to remove a suppressed contact). Loops also auto-suppresses hard bounces and complaints going forward.
Run both providers in parallel, then cut over per email type. Keep Brevo live, move one email type at a time (password reset first, then receipts, then marketing), and watch delivery events for each before moving the next. Do not switch everything at once.
Mind the marketing audience flag. POST /v1/transactional accepts addToAudience. Leave it off for account mail like password resets. Set it to true only when you intend to add the recipient to your marketing audience.
Respect the rate limit. Loops allows 10 requests per second per team and returns 429 when you exceed it, so back off and retry. For sends and events you can pass an Idempotency-Key header (up to 100 characters). A key reused within 24 hours returns 409, which protects against duplicate sends on retry.
Keep template variables and code in sync. Because copy lives in the published template and code passes only dataVariables, a renamed variable in the editor must match the key in your code, or the send fails.
Common questions
Can I keep sending htmlContent from my code like Brevo?
Does Loops replace Brevo SMS and WhatsApp?
How long does the migration take?
Will my Brevo unsubscribes carry over?