I am busy building a SaaS product that has to reliably send large numbers of emails. At ZappiStore we use the Ruby on Rails stack extensively. I think Rails is an excellent choice for building web applications. Rails makes you super productive and the community support is excellent. Naturally I want to use Rails for the web application layer of the system. There are a couple of different options built into Rails for sending emails.
Since the web application layer is written in Ruby and I have a microservice written in Node.js, these need to pass data between one-another in some way. The communication solution should have the following properties:
Robust. If a service goes down, the message should be persisted in the queue until it is handled.
Decoupled. The individual microservices should ideally not have any direct knowledge of each other. Each service should communicate via the message broker.
Enter RabbitMQ. Rabbit has been around for a long time and has proven to be a high-performance and reliable message queue. The idea is to use Ruby to push a message onto the queue and then pop the message off of the queue in the Node.js microservice.
Getting Started with RabbitMQ
For those of us that use OS X, installing RabbitMQ is easiest done with Homebrew:
brew install rabbitmq
Once RabbitMQ is installed, start it up as a service with the following command:
brew services start rabbitmq
The Ruby Layer
In order to connect to RabbitMQ from Ruby we are going to use the bunny gem. Add it to your Gemfile:
gem 'bunny', '~> 2.3.1'
Then run a bundle install.
The bunny gem gives us a lot of great functionality out of the box like robust connection handling. If the connection to RabbitMQ drops, bunny will attempt to reconnect until the connection is restored. The bunny gem also gives us an easy API for leveraging RabbitMQ from within our application.
The connection to RabbitMQ should be established at application startup and remain open for the duration of the application’s lifetime. To this end we create a MessagingService singleton class:
By using the Singleton pattern for the MessageService class we can ensure that the connection is not closed resulting in us having to reconnect every time we want to send an email. The EmailQueueService class uses the connection provided by the MessageService class to create a queue for our email pipeline:
The AccountActivationService class is specific to sending account activation emails when new users sign up:
The Node Layer
The package.json for the project is as follows:
The amqplib library is used to communicate with RabbitMQ, lodash as a helper library, mime to get the mime types of the email attachments, request for sending the HTTP requests, winston for logging and yamljs to parse the config.yml file containing settings.
The bulk of the work is done in the worker.js file:
The worker is the started using the main.js file:
Putting it all together
In order to send email accross the queue, the following code is executed from the Rails UsersController: