Delaying all your mail with DelayedJob

Messing with your mail in Rails can be a bit of a pain because of the way ActionMailer was designed. Delayed Job has something called a PerformableMailer, which helps you send out individual mails by calling delay on your mailers:
FriendMailer.delay.send_my_friend_a_message(@friend, 'hi')
But I wanted to send all my mail through delayed_job, and I didn’t want to be running around sticking .delay in everywhere. So here’s what I’ve got.
# lib/delayed/mail_delivery.rb
 
module Delayed
  class MailDelivery
    class << self
      attr_accessor :method
    end
 
    def initialize(settings)
    end
 
    def deliver!(mail)
      self.delay(:queue => 'mail').pass_to_delivery_method(mail.to_yaml)
    end
 
    def pass_to_delivery_method(message_as_yaml)
      method = self.class.method
      klass = ActionMailer::Base.delivery_methods[method]
      settings = ActionMailer::Base.send(:"#{method.to_s}_settings")
      klass.new(settings).deliver!(Mail::Message.from_yaml(message_as_yaml))
    end
  end
end

The idea is we’re going to ask Rails to use a custom delivery method called :delayed. What this delivery method does is serialize the mail message using Mail::Message’s to_yaml function, and enqueue it using Delayed Job. When the time comes to send the message, it deserializes it and passes it to the real delivery method.

Now all we have to do is wire it up. First we tell rails about our new delayed delivery method, and then we tell our delayed method that the real sending should be done with SMTP.

# config/initializers/delayed_mail.rb
 
require 'delayed/mail_delivery'
ActionMailer::Base.add_delivery_method :delayed, Delayed::MailDelivery
Delayed::MailDelivery.method = :smtp
The only thing left to do is to tell Rails to use this method 🙂
# config/application.rb
 
module FriendSend
  class Application < Rails::Application
 
    ...
 
    # mailer
    config.action_mailer.delivery_method = :delayed
  end
end

Don’t forget to run the Delayed Job background process.

Note: this post assumes that you’re somewhat familiar with using delayed_job. If you’re not, check it out on asciicasts or github (for a more up-to-date version).