Posterous theme by Cory Watilo

Canceling mail delivery from within mailer

In project I’m working on mail notifications are handled by resque_mailer, and are sent from models in after_create callback. When you call deliver – actually you are calling it on Resque::Mailer::Rails3MailerProxy – it puts mailer class name and arguments into the queue. Argument in our case is id of some stored event, for which user should be notified. Then background worker takes this data from queue, creates mailer, and calls

@mailer_class.send(:new, @action, *@args).message.deliver

And usually this isn’t bad.

But now we are creating records within DB transaction. Under some circumstances transaction could be rolled back. Oh wait! after_create doesn’t know anything about it, and it queues mail notification with object’s id… which isn’t available anymore!

The problem is – how to abort mail delivery from within mailer?

First thing you’d try is return false from mailer action. But mailer will anyway render template, and you’ll get the error.

I didn’t find any solution, so I dug in the mailer code and came with simple wrappers.

First, deliver is actually called on Mail::Message object. In case of error you should replace it with some dummy:

module BulletproofMailer
  class BlackholeMailMessage < Mail::Message
    def self.deliver
      false
    end
  end
end

Ok, but how to feed it into mailer? Here comes another wrapper where we are replacing self.message:

module BulletproofMailer
  class AbortDeliveryError < StandardError
  end

  class Base < ActionMailer::Base

    def abort_delivery
      raise AbortDeliveryError
    end

    def process(*args)
      begin
        super *args
      rescue AbortDeliveryError
        self.message = BulletproofMailer::BlackholeMailMessage
      end
    end
  end
end

Using these wrapper mailer would look like this:

class EventMailer < BulletproofMailer::Base
  include Resque::Mailer
  def event_created(event_id)
    begin
      @event = CalendarEvent.find(event_id)
    rescue ActiveRecord::RecordNotFound
      abort_delivery
    end
  end
end

PS: Yeah, I know we should rewrite mail callbacks, and we will do it. But a bit later.