← Back to Blog
From Resend to Brevo: My OTP API Journey Learning

From Resend to Brevo: My OTP API Journey

What started as a simple OTP verification feature turned into a small lesson in choosing the right email API. Here’s how I moved from Resend to Brevo, what pushed me to make the switch, and what I learned along the way.

 

There are some features that look ridiculously simple when you first think about them.

OTP verification was one of them.

The idea seemed straightforward:

Generate an OTP → send it to the user's email → verify it → let them continue.

Simple enough.

So, naturally, I thought, how hard could it be?

Turns out, the OTP itself wasn't really the difficult part. The more interesting problem was finding an email service that actually fit the way I wanted to use it.

And that eventually took me from Resend to Brevo.


Where It Started

While working on my project, I wanted to add email-based OTP verification.

The goal was pretty basic. When a user needed to verify their email, the application would generate a temporary OTP, send it through an email service, and then verify the code entered by the user.

From the application side, the flow looked something like this:

The backend would take care of generating and validating the OTP, while the external email service would handle actually delivering the email.

I didn't want to build some complicated email infrastructure myself.

I just wanted an API.

Something I could plug into Django, configure, and move on with my life.

So I started looking at email APIs.


First Stop: Resend

That's where Resend came in.

The API was clean, the documentation was easy to understand, and integrating it into a project wasn't particularly painful.

For a developer who just wants to send transactional emails without spending half the day figuring out an unnecessarily complicated system, that simplicity is pretty nice.

I connected it to my project and started working on the OTP flow.

At this point, everything seemed fine.

Generate the OTP.

Create the email.

Send it through the API.

Verify the OTP.

Done.

Except... not quite.


Then Came the Problem

The problem wasn't that Resend was bad.

The problem was that its available setup didn't really match what I needed.

The free/onboarding limitations around sending emails became a problem for the way I wanted to use the service. In particular, I needed something that would work properly for my own transactional email flow rather than being limited to an onboarding-style setup.

And that's one of those things you don't always think about when choosing an API.

You see:

"Free tier."

And your brain immediately goes:

"Perfect."

Then you actually start building.

And suddenly you're reading documentation, checking sending restrictions, looking at sender requirements, and wondering why you didn't check all of this before writing the integration.

Classic developer experience.


So I Started Looking Again

At this point, I didn't want to completely redesign the OTP system.

The OTP logic itself was fine.

The application knew how to:

  • generate an OTP
  • store it temporarily
  • verify the submitted code
  • reject invalid codes
  • handle expiration

The Problem I Didn't Notice Until Deployment

The interesting part is that I didn't actually discover the issue while developing locally.

Everything seemed to be working fine.

Then I deployed the application.

At some point, a user submitted the form—and the expected email never arrived. That was the moment I realized something was wrong.

I started tracing the request from the form submission back through the backend and eventually reached the email API. That's when I discovered that the way I had set things up wasn't going to work for the actual production use case.

So the problem wasn't just:

"My OTP email isn't being sent."

It became:

"Why did this work during development, but fail when an actual user tried it?"

And honestly, that's one of those bugs you don't really appreciate until you're staring at your deployed application wondering what happened.

That incident is what pushed me to properly investigate the email provider, understand its limitations, and eventually look for an alternative.

The part I wanted to replace was basically the email delivery layer.

That distinction was important.

Instead of thinking:

"I need to rebuild my OTP system."

I started thinking:

"I just need a better email provider for this system."

That made the problem much smaller.

I started looking at alternatives and eventually came across Brevo.


Enter Brevo

Brevo looked much closer to what I needed for transactional email.

The important thing for me wasn't having a massive list of features.

I mainly cared about a few things:

  • Can I send transactional emails?
  • Is the API straightforward?
  • Can I integrate it with Django?
  • Does it work for OTP verification?
  • Is there a reasonable option for development?
  • Can I use my own sender configuration properly?

The answer looked promising enough to give it a try.

So I decided to switch.


Changing the Integration

One thing I learned from this whole process is that choosing a service doesn't necessarily mean your entire application has to depend heavily on it.

I had already separated the OTP logic from the actual email-sending logic.

So instead of rewriting everything, I could essentially replace one part of the system.

The basic architecture became:

The OTP itself didn't care whether the email was being sent through Resend, Brevo, or something else.

It was just a temporary verification code.

That meant the provider could change without forcing me to rethink the entire authentication flow.

And honestly, that's probably one of the better architectural decisions I made during this little detour.


The OTP Flow

The final flow was fairly straightforward.

When the user requests verification, the backend generates a temporary OTP.

Something like:

The interesting part is that there are actually two different problems here.

The first is OTP management.

The second is email delivery.

I initially treated them as one problem.

They're not.

Keeping them separate made the whole implementation easier to change later.


What Went Wrong Along the Way

Of course, switching providers wasn't completely magical.

There were configuration issues.

There were API details to figure out.

There were environment variables to update.

There were sender settings to configure.

And, because this is development, there were definitely moments where something didn't work and I stared at the screen wondering what exactly I had managed to break this time.

That's part of the process, though.

A lot of development isn't:

Write code → everything works → go home.

It's more like:

Write code → test → something breaks → read documentation → change something → test again → break something else → question your life choices → finally fix it.

And somehow that's also the fun part.


What I Learned From the Switch

The biggest thing I learned wasn't actually about Resend or Brevo.

It was about choosing third-party services.

Before this, I mostly looked at whether an API was easy to integrate.

Now I pay more attention to the things that come after the integration:

1. Free doesn't always mean suitable

An API can have a free tier and still not be useful for your particular use case.

Always check what the free tier actually allows.

2. Read the restrictions before building around a service

It is much easier to spend five minutes reading documentation than to spend two hours replacing an API after you've already built everything around it.

Not that I learned this the easy way.

3. Keep external services replaceable

This was probably the most useful lesson.

If your application mixes business logic directly with a third-party service everywhere, changing providers becomes painful.

If you isolate that integration, changing providers becomes much easier.

4. Small projects still teach real engineering lessons

This started as:

"I just need to send an OTP."

It ended up teaching me about service limitations, transactional email, API abstraction, configuration, and architecture.

That's something I like about building projects.

You start with a small feature.

Then the feature starts asking questions.

And if you actually follow those questions, you usually learn something useful.


Was Switching Worth It?

For my project, yes.

The point wasn't that Brevo was universally better than Resend.

It was simply a better fit for what I was trying to accomplish at that point in the project.

That's an important distinction.

There probably isn't one perfect API for everyone.

A service that works perfectly for one developer might be completely inconvenient for another depending on the project's requirements, budget, scale, and email needs.

For me, the switch solved the immediate problem and, more importantly, taught me to look beyond the shiny "easy API" part of a service.


Looking Back

If I had to do it again, I'd probably spend a little more time researching the email provider before writing the integration.

But then again, that probably wouldn't have taught me as much.

Sometimes you understand why something matters only after you run into the problem yourself.

The original goal was just to make OTP verification work.

Instead, I ended up learning that the hardest part isn't always writing the feature.

Sometimes it's choosing the tiny piece of infrastructure that the feature depends on.

And sometimes you have to replace that piece halfway through.

Annoying?

A little.

Useful?

Definitely.

Would I do it again?

Probably.

Just with a little more research first.

Because apparently "I'll just use this API" is not a complete architecture plan.

Who knew?


Final Takeaway

My journey from Resend to Brevo wasn't some massive rewrite or revolutionary engineering decision.

It was just one of those small problems that showed me something bigger:

Build your application so that changing your mind is possible.

Because sooner or later, you probably will.

And when that happens, your future self will appreciate the fact that you didn't make one API the entire personality of your application.