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:

module Devise
  module Models
    module Confirmable
      handle_asynchronously :send_confirmation_instructions
    end
  
    module Recoverable
      handle_asynchronously :send_reset_password_instructions
    end
    
    module Lockable
      handle_asynchronously :send_unlock_instructions
    end
  end
end

(Forked from robotmay via Stack Overflow)

This code utilizes delayedjob's `:handleasynchronouslywrapper, 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 myconfig/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:

class PasswordResetMailer < Devise::Mailer
  def reset_password_instructions(record)
    Delayed::Job.enqueue PasswordResetJob.new(record.id)
  end
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.

class PasswordResetJob < Struct.new(:id)
  def perform
    user = User.find(id)
    Devise::Mailer.reset_password_instructions(user).deliver
  end
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:

Devise.setup do |config|
  #...
  config.mailer = "PasswordResetMailer"
  #...
end

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