- Published on
Migrating from Brevo to Resend in a Next.js App
- Authors

- Name
- Ritwik Lodhiya
I originally used Brevo (formerly Sendinblue) to handle contact form submissions on my site, and it worked fine locally, but I kept running into issues with it when deployed to Vercel. Resend offers a much simpler developer experience — built for frameworks like Next.js and Vercel.
Here’s how I made the switch.
📦 Installing Resend
yarn add resend
🛠️ Brevo: What I Had Before
import { TransactionalEmailsApi, SendSmtpEmail } from '@getbrevo/brevo'
const api = new TransactionalEmailsApi()
api.setApiKey('apiKey', process.env.BREVO_API_KEY!)
await api.sendTransacEmail(new SendSmtpEmail({
to: [{ email: 'hello@rlodhiya.dev' }],
sender: { email: 'system@rlodhiya.dev', name: 'RL-System' },
subject: 'Contact Form',
htmlContent: '<p>Message</p>',
}))
🔁 Replaced with Resend
import { Resend } from 'resend'
const resend = new Resend(process.env.RESEND_API_KEY)
await resend.emails.send({
from: 'RL-System <system@rlodhiya.dev>',
to: ['hello@rlodhiya.dev'],
subject: 'Contact Form',
html: '<p>Message</p>',
})
📬 Reusable HTML Builder
// message-builder.ts
export function buildGetInTouchHtml({ fullName, message }: GetInTouchArgs) {
return `<div><p><strong>${fullName}</strong>: ${message}</p></div>`
}
Both Brevo and Resend now use this for consistency.
🧠 Generic EmailService Wrapper
I created a service that reads from EMAIL_PROVIDER in .env and routes the request to the correct implementation. This allows for an easy fallback, or potential future experimentation with Brevo.
const service = new EmailService()
await service.sendGetInTouchMessage(args)
✅ Why I Switched
Resend feels tailor-made for modern React and serverless projects. Type-safe, clean, and no extra ceremony. It also plays nicely with my Vercel deployment.
It's looking good so far! Try it out: Contact Me
May 2026 Update: Resend v6 Library
Since this migration, the Resend Node.js library has moved to v6.12.3. My original contact-form implementation still uses the same core pattern: instantiate Resend, then call resend.emails.send(...).
import { Resend } from 'resend'
const resend = new Resend(process.env.RESEND_API_KEY)
const { data, error } = await resend.emails.send({
from: 'RL-System <system@rlodhiya.dev>',
to: ['hello@rlodhiya.dev'],
replyTo: 'reply@rlodhiya.dev',
subject: 'Contact Form',
html: buildGetInTouchHtml({ fullName, message }),
})
if (error) {
throw error
}
return data
The notable library updates are mostly maintenance and type improvements:
- Fixed a
payloadtypo in contact overload signatures - Added the missing
suppressedwebhook event type to the SDK interface - Updated the
svixdependency to address a security advisory
Resend has also added more platform and API capabilities since this post, including API request logs, an official CLI, webhook management APIs, sent-email listing, pagination for list endpoints, batch validation modes, batch idempotency keys, and automatic plain-text email generation.
The EmailService abstraction made this update smaller than it could have been. The rest of the app still calls sendGetInTouchMessage(args), while the external provider-specific work stays inside the Resend implementation. That kept the library update focused on the boundary where the app talks to Resend instead of forcing contact form or route changes.
The migration approach still holds up: keeping Resend behind a small email service wrapper means I can adopt those newer features without rewriting the original contact-form flow.