Sending Devise emails with Delayed_job

I use Delayed_job on Ducky Guidance to send our email notifications in the background. I recently switched our authentication gem from authlogic to Devise to take advantage of the baked in password reset functionality (and to allow us to use other built in features down the road as well).

After migrating our users from authlogic to Devise, the first order of business was to set up Delayedjob to send password reset emails in the background. After scouring the web, the most common advice I found was to have Devise utilize delayedjob to do the following:

 1 module Devise
 2   module Models
 3     module Confirmable
 4       handle_asynchronously :send_confirmation_instructions
 5     end
 6   
 7     module Recoverable
 8       handle_asynchronously :send_reset_password_instructions
 9     end
10     
11     module Lockable
12       handle_asynchronously :send_unlock_instructions
13     end
14   end
15 end

(Forked from robotmay via Stack Overflow)

This code utilizes delayed_job's :handle_asynchronously wrapper, I was met with mixed results. More often than not, I was met with NoMethodErrors due to nil objects. The issue was hard to track down, as I could get it to work sometimes, depending on where the above snippet was located. I had it working consistently on my laptop by adding the snippet to the end of my config/initializers/devise.rb file, but when I tried it on our staging server, the nil objects reared their heads again.

There is another way to have Devise send its email in the background, and that is by using a custom mailer with Devise. There is a configuration parameter in the Devise initializer that will allow you to set the class you want to use for emails. I went about using the following custom emailer:

1 class PasswordResetMailer < Devise::Mailer
2   def reset_password_instructions(record)
3     Delayed::Job.enqueue PasswordResetJob.new(record.id)
4   end
5 end

The above code inherits from Devise::Mailer and overrides the reset_password_instructions method (which is the only one I care about right now). I used Delayed_job to enque a custom job called PasswordResetJob.

1 class PasswordResetJob < Struct.new(:id)
2   def perform
3     user = User.find(id)
4     Devise::Mailer.reset_password_instructions(user).deliver
5   end
6 end

The job takes the user's id, retrieves that record, and uses the default Devise mailer to reset the password and send the user the reset instructions.

After setting up the custom mailer and job, I just had to configure devise to use that mailer. I added the following line to my config/initializers/devise.rb:

1 Devise.setup do |config|
2   #...
3   config.mailer = "PasswordResetMailer"
4   #...
5 end

And was on my way. The mailer works perfectly, sending password resets in the background without error.