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.