Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detect action mailer delivery failures in after_action callbacks

I am using an after_action callback in my mailers to record that email was sent. The emails are sent through Delayed Job. This works, except when we are unable to reach the remote server - in which case, the email is not sent, but we record that it was. Delayed Job retries the email later, and it is successfully delivered, but we've then recorded that two emails were sent.

It looks something like this:

class UserMailer < ActionMailer::Base

  after_action :record_email

  def record_email
   Rails.logger.info("XYZZY: Recording Email")
   @user.emails.create!
  end

  def spam!(user) 
   @user = user
   Rails.logger.info("XYZZY: Sending spam!")
   m = mail(to: user.email, subject: 'SPAM!')
   Rails.logger.info("XYZZY: mail method finished")
   m
  end
end

I call this code like this (using delayed job performable mailer):

UserMailer.delay.spam!( User.find(1))

When I step through this in a debugger, it seems that my after_action method is called before the mail is delivered.

[Job:104580969] XYZZY: Sending spam!
[Job:104580969] XYZZY: mail method finished
[Job:104580969] XYZZY: Recording Email
Job UserMailer.app_registration_welcome (id=104580969) FAILED (3 prior attempts) with Errno::ECONNREFUSED: Connection refused - connect(2) for "localhost" port 1025

How can I catch network errors in my mailer methods and record that the email attempt failed, or do nothing at all? I'm using Rails 4.2.4.

like image 628
John Naegle Avatar asked Mar 21 '16 20:03

John Naegle


1 Answers

This is what I came up with, I would love to have a better way.

I used the Mail delivery callback:

delivery_callback.rb

class DeliveryCallback
  def delivered_email(mail)
    data = mail.instance_variable_get(:@_callback_data)
    unless data.nil?
      data[:user].email.create!
    end
  end
end

config/initializes/mail.rb

Mail.register_observer( DeliveryCallback.new )

And I replaced my record_email method:

class UserMailer < ActionMailer::Base

  after_action :record_email

  def record_email
    @_message.instance_variable_set(:@_callback_data, {:user => user}) 
  end
end

This seems to work, if the remote server is not available, the delivered_email callback is not invoked.

Is there a better way!?!?

like image 140
John Naegle Avatar answered Sep 17 '22 15:09

John Naegle