DEV Community

Cover image for RabbitMQ: The Superhero My API Didn’t Know It Needed
karabo seeisa
karabo seeisa

Posted on

RabbitMQ: The Superhero My API Didn’t Know It Needed

The Problem: Hanging Requests, Delivered Emails

In my Node.js API, users register and receive a verification email. Simple, right?

But something strange kept happening:

  • The email would be sent
  • But the HTTP request would hang
  • The client got no response
  • My logs showed timeouts
  • And eventually… crashed sockets

After digging, I realized:

The SMTP email-sending process was blocking the main thread just long enough to let the HTTP connection time out.

This was a race condition: my API was waiting for the SMTP server to respond, while the client’s connection was waiting for me.


The Insight: Decouple the Work

I realized I was making the email-sending part synchronous, tying the API response to something slow (SMTP).

That’s when I remembered the concept of queuing so I did some research and found that personally I liked RabbitMQ.


How I Fixed This Issue

Here’s what I did:

  • The /register route publishes a message to a RabbitMQ queue (emailQueue)
  • A separate email worker subscribes to the queue
  • The worker handles the SMTP email process asynchronously
  • Meanwhile, the API responds immediately to the client

Here's What Achieved:

  • No more hanging sockets
  • Emails still go out
  • Race condition avoided
  • Clean separation of concerns

Why I Chose RabbitMQ?

I could’ve tried background threads or retry logic. But RabbitMQ gave me:

  • Message durability
  • Retry control
  • A proper job queue
  • A decoupled architecture I can scale later

And now, I can apply this same technique for:

  • Password resets
  • Event logs
  • Audit trails
  • Even SMS messages or 2FA

Call me a nerd but I think that's cool.


Implementation:

This is the publisher - gets the request to send a verification email, drops it into a queue, and the worker handles the rest . simple, fast, and scalable.

export const publishToQueue = async (queue: string, message: any)=>{ if(!channel) throw new Error("RabbitMQ channel not initialized"); channel.sendToQueue(queue,Buffer.from(JSON.stringify(message)),{persistent: true}); } 
Enter fullscreen mode Exit fullscreen mode

This is the worker - It consumes messages form the queue and sends the email asynchronously,without delaying the HTTP response to the client .

That means users don't wait around for an email to send = much better UX if you ask me

channel.consume("emailQueue",async (msg) =>{ if(msg !== null){ const {email,verifyToken} = JSON.parse(msg.content.toString()) await sendVerificationEmail(email,verifyToken); channel.ack(msg) } }) 
Enter fullscreen mode Exit fullscreen mode

Final Thoughts

If your API does more than just respond to users — like sending emails or talking to 3rd parties — don’t make it wait.

RabbitMQ helped me move that responsibility out of the request cycle and made my code faster, safer, and easier to maintain.

I also plan to use it in the near future.


Let’s Connect

Have you hit similar issues in your backend? let me know how you tackled async tasks or race conditons.

Here's how I implemented it might spark something in your project :Github

Top comments (0)