DEV Community

Cover image for Implementing an email queue with bullJs and hosting on Heroku!
Victory Akaniru
Victory Akaniru

Posted on

Implementing an email queue with bullJs and hosting on Heroku!

The problem...

If you've ever picked up a task that reads like this Setup an email service with SendGrid and nodemailer you probably did what most of us would do... Setup SendGrid for production and nodemailer for development and test environments. If you did that successfully and stopped no one would fault you but oftentimes email sending in some applications require just a bit more to keep em running smoothly. Few questions we often forget to ask is

  • How does the increase in volume affect my email setup?
  • Would it hold out or work when we have 10,000 users, sending at least one email every minute?
  • What happens when my email fails to send on the first attempt for various reasons...

Well, in this article we'll be looking at setting up an email queuing system using bullJs in a nodeJs application and also getting this setup to a production-like environment using Heroku.

Prerequisite

  • Basic knowledge of Javascript and nodeJs
  • Heroku account(you can sign up here)
  • Clone this repo 👉🏽 https://github.com/vic3king/bulljs-email-setup.git and follow the readme setup instructions.
  • Redis server running on localhost:6379. You can set one up quickly by following the instructions here

You can also set up your own nodeJs application the way you like it and integrate the code from this example and it should work regardless. But for this article, I will be working with the cloned repo.

The cloned repo contains some boilerplate code set up with node, express, and Sequelize. I added a registration endpoint that requires the user to input their username and email address(server/controllers/auth) to register. If you followed the setup instructions on the readMe you should be able to hit the registration endpoint at http://127.0.0.1:3333/v1/auth/register and register a user.
Alt Text

I have SendGrid and nodemailer preconfigured on the application as well in form of a function we can call whenever we need to send an email (server/config/emailSetup.js), I also added a base email template function that takes in a username, email, and a link as arguments. What we want to do now is to make sure that when a user registers they get an email that will be queued and processed by the bullJs package.

Bull is a Node library that implements a fast and robust queue system based on redis.

Although it is possible to implement queues directly using Redis commands, this library provides an API that takes care of all the low-level details and enriches Redis' basic functionality so that more complex use-cases can be handled easily. Read more

Bull Implementation

Navigate to the server/config folder and add a file called bullConfig.js.Paste in the following code.

import Queue from 'bull';
import dotenv from 'dotenv';

dotenv.config();

const { REDIS_URL } = process.env;

// Initiating the Queue with a redis instance
const sendMailQueue = new Queue('sendMail', REDIS_URL);

export default Sendmail queue;
Enter fullscreen mode Exit fullscreen mode

Make sure your .env file contains REDIS_URL='redis://127.0.0.1:6379'. In this file, we're importing Queue from our package bull. with that, we create a new instance of the bull queue class and called it sendMailQueue. We could create multiple queues for a different use case like

const imageQueue = new Queue('image transcoding');
Enter fullscreen mode Exit fullscreen mode

Make sure you have a Redis server running in the background

Next, we need to create a notifications.js file in the server/helper folder. In this file, we'll introduce two key concepts from bullJs producer and consumer. we'll also create a function that takes in a receiver's email address, name, and a link to verify their email address. we could extend this implementation by adding functions like a forgotPassword function that takes the same parameters as the registrationEmail function. for each new notification we send out, we need to trigger the producer by calling sendMailQueue.add(data, options);.

import emailService from '../config/emailSetup';
import sendMailQueue from '../config/bullConfig';
import template from './template';

/**
 *
 * @param {*} emailTo
 * @param {*} link
 * @param {*} name
 * @returns {*} sends an email to a new user
 */
const registrationEmail = (emailTo, link, name) => {
  const subject = 'Welcome to Karneek';
  const body = `<p>Dear ${name},</p>
  <p>We are thrilled to have you.</p>
  <p>Some random message with link</p>
      <a href="${link}" class="button">Confirm email</a>`;
  const message = template(subject, body, emailTo);

  const options = {
    attempts: 2,
  };
  const data = { emailTo, subject, message };

  // Producer: adds jobs to que, in this case emails to be sent out upon signup
  sendMailQueue.add(data, options);
};

// Consumer: this gets called each time the producer receives a new email.
sendMailQueue.process(async job => {
  emailService.mailSender(job.data);
});

const Notifications = { registrationEmail };

export default Notifications;
Enter fullscreen mode Exit fullscreen mode

Finally, we need to call the registrationEmail function in our registration route. Navigate to server/controllers/auth.js. Import our notifications file

import notifications from '../helpers/notifications';
Enter fullscreen mode Exit fullscreen mode

In the registerUser function, just after the verificatioToken variable add

      const { REGISTRATION_URL } = process.env;
      const verificationLink = `${REGISTRATION_URL}?token=${verificationToken}`;
      await notifications.registrationEmail(email, verificationLink, username);
Enter fullscreen mode Exit fullscreen mode

That's it. To test things our run the server npm run start:dev and create a new user! On your console, you should see

Alt Text

Hosting this setup on Heroku

The challenge with this setup comes when we need to host this project on a remote server like Heroku. To get this working, we need to have an instance of Redis running by the side on Heroku. First, we need to add an addon by Heroku called Heroku Redis, we also need to add Heroku Postgres because this project uses a Postgres DB.

Navigate to your dashboard on Heroku, and create a new app. After that navigate to the overview tab of the newly created Heroku app and click on Configure Add-ons

Alt Text

This would navigate to a new page with a search box. Search for and add Heroku Redis, and Heroku Postgres

Alt Text

Copy the name of your Heroku app and on your terminal run the following commands.

heroku git:remote -a <name of heroku app>
heroku addons | grep heroku-redis
heroku addons:create heroku-redis:hobby-dev -a <name of heroku app>
Enter fullscreen mode Exit fullscreen mode

After the last command, you should see something close to this

Alt Text

You need to copy your new Redis addon name at this point. for me, it was redis-deep-25660 (see screenshot)

run the next commands

heroku addons:info <your addon name> 
heroku config | grep REDIS
git push heroku master 
Enter fullscreen mode Exit fullscreen mode

If you're seeing anything like this on your logs, then you're done! 😳
Alt Text

To learn more about how Heroku Redis works ... https://devcenter.heroku.com/articles/heroku-redis#provisioning-the-add-on

Conclusion

In this article, we've been able to use bullJs for email queueing and also host our tiny app on Heroku by provisioning and running a Redis server on our Heroku app. I hope you found this useful, If you run into any problems you can check out the complete code here or leave a comment.

Top comments (7)

Collapse
 
notrab profile image
Jamie Barton

Great article! It may be worth covering reusing the Redis instance across client/publishers to avoid any max concurrent connection errors

Collapse
 
vic3king profile image
Victory Akaniru

Thanks Jamie. I like the sound of that. I'll try and come up with something.

Collapse
 
sudesh1122 profile image
Sudesh Chaudhary

This might me helpful for reusing redis connections: github.com/OptimalBits/bull/blob/d...

Collapse
 
davidokonji profile image
David Okonji

Awesome read 🎯 thanks for this guide

Collapse
 
_criztus profile image
Nmeregini Vincent

thanks man this awesome, a question would be how to handle error when sending messages

Collapse
 
iam_chiike profile image
Chike A. Ozulumba

Thanks for this.
Good read

Collapse
 
slidenerd profile image
slidenerd

as much as I appreciate the post, you have not shown anything about how bounce or retry would actually work