Using Sidekiq to send emails asynchronously

Posted on
Sidekiq

If your web application needs to send out emails, the best practice is to send them asynchronously in a background task. This usage is so common that Rails 4 added an asynchronous Action Mailer, but the feature will no longer be making the original 4.0 release. In spite of that, the asynchronous Action Mailer would have been a temporary solution for developers until they were ready to run their own dedicated queue.

A lot of Ruby developers are familiar with queues, such as DelayedJob and Resque. While these queues have been predominant the last couple of years, my tool of choice is Sidekiq, a Redis backed queue by Mike Perham. Sidekiq advertises that a single process can do the work of 20 Resque or DelayedJob processes. Another benefit of Sidekiq is the inclusion of extensions, which allows for testing and delayed mailers.

Getting Started

Add Sidekiq to your Rails project by including it to your Gemfile and run the bundle command:

# Gemfile
gem 'sidekiq'

To run Sidekiq, run the following:

$ bundle exec sidekiq

Sending Delayed Emails

Sidekiq supports sending emails asynchronously with Action Mailer out of the box. It does this by including three methods to the ActionMailer module.

.delay

The method that you will likely use the most is .delay. Calling .delay from an Action Mailer will result in the email being added to the DelayedMailer queue for processing.

Example:

SubscriptionMailer.delay.welcome(@user.id)

Note the absence of .deliver in the above code snippet, as that execution is performed by the DelayedMailer.

.delay_for(interval)

Sidekiq also supports the delayed delivery of emails by providing a specified interval to the .delay_for method.

Example:

UserMailer.delay_for(1.day).status_report(@user.id)

.delay_until(timestamp)

The last Action Mailer method added by Sidekiq is .delay_until. Sidekiq will wait until the provided time to send the email.

Example:

UserMailer.delay_until(3.days.from_now).status_report(@user.id)

How about Devise emails?

Devise has its own mailer for authentication emails. You can enable asynchronous emails for Devise by using the devise-async gem. Note that emails queued via devise-async are added the mailer queue by default.

To setup devise-async, add it to your Gemfile and run the bundle command:

# Gemfile
gem 'devise-async'

Add the :async option to the devise call in your authentication model:

class User < ActiveRecord::Base
  devise :database_authenticatable, :async, :confirmable #...
end

Finally, create an initializer to set Devise::Async.backend to use Sidekiq:

# config/initializers/devise_async.rb
Devise::Async.backend = :sidekiq

Testing

Sidekiq comes with some great test/spec helpers to make testing against it easy.

In spec/spec_helper.rb, requiring sidekiq/testing forces Sidekiq to not push jobs to Redis, but an array named jobs for testing purposes.

# spec/spec_helper.rb
require 'sidekiq/testing'

To ensure emails are correctly enqueued in Sidekiq, you can test against the jobs size of the Sidekiq::Extensions::DelayedMailer:

expect { SubscriptionMailer.delay.welcome(user.id)}.to change(Sidekiq::Extensions::DelayedMailer.jobs, :size).by(1)

Further Reading

002

This post is by Kevin Faustino. Kevin is the Chief Craftsman of Remarkable Labs and also the founder of the Toronto Ruby Brigade.


Comments

comments powered by Disqus