Detecting mail delivery failures in after_action callbacks

I use the after_action in my mail servers to record this message. Emails are sent through the Delayed Job. This works, except when we cannot contact the remote server - in this case the letter is not sent, but we record what it was. The delayed job resends the email later and it was delivered successfully, but then we 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 as follows (using deferred work performed by the mail program):

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

When I get to this in the 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 the mail program methods and record that the email attempt failed, or do nothing at all? I am using Rails 4.2.4.

+7
ruby-on-rails ruby-on-rails-4 delayed-job actionmailer
source share
3 answers

This is what I came up with, I would like 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 

configuration / initializes / mail.rb

 Mail.register_observer( DeliveryCallback.new ) 

And I replaced the 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 unavailable, the deliver_email callback is not called.

Is there a better way!?!?

+2
source share

The debug messages you showed make perfect sense - the action of the mail program ends immediately, because the mail action itself is asynchronous and processed by the Delayed task in a completely different process. Thus, the mail program class itself does not know how the mailing ended.

Instead, I think you need to complete the deferred task hooks . You will have to rewrite your mail and calls to send emails, though.

I have not tested it completely, but something in the following lines should work:

 class MailerJob def initialize(mailer_class, mailer_action, recipient, *params) @mailer_class = mailer_class @mailer_action = mailer_action @recipient = recipient @params = params end def perform @mailer_class.send(@mailer_action, @recipient, *@params) end def success(job) Rails.logger.debug "recording email!" @recipient.emails.create! end def failure(job) Rails.logger.debug "sending email to #{@recipient.email} failed!" end end 

MailerJob is a custom job that must be run by a Delayed job. I tried to make this as general as possible, so it takes the class of the mail program, the action of the mail program, the recipient (usually the user) and other optional parameters. It also requires recipient have an emails association.

Two hooks are defined in the task: success upon successful completion of the mail action that creates an email record in the database, and the other for registration failure. Actual dispatch is performed in the perform method. Please note that the delayed method is not used inside it, since the entire task is already highlighted in the background. Queue delay for calls.

To send mail using this custom task, you must insert it in the specified task , for example:

 Delayed::Job.enqueue MailerJob.new(UserMailer, :spam!, User.find(1)) 
0
source share

Try the following:

 class UserMailer < ActionMailer::Base # after_action :record_email def record_email Rails.logger.info("XYZZY: Recording Email") @user.emails.create! end def spam!(user) begin @user = user Rails.logger.info("XYZZY: Sending spam!") m = mail(to: user.email, subject: 'SPAM!') Rails.logger.info("XYZZY: mail method finished") m rescue Errno::ECONNREFUSED record_email end end end 
-one
source share

All Articles