This blog is part of our Rails 5.2 series.

Rails allows sending emails asynchronously via Active Job.

Notifier.welcome(User.first).deliver_later

It uses ActionMailer::DeliveryJob as the default job class to send emails. This class is defined internally by Rails.

The DeliveryJob defines handle_exception_with_mailer_class method to handle exception and to do some housekeeping work.

def handle_exception_with_mailer_class(exception)
  if klass = mailer_class
    klass.handle_exception exception
  else
    raise exception
  end
end

One might need more control on the job class to retry the job under certain conditions or add more logging around exceptions.

Before Rails 5.2, it was not possible to use a custom job class for this purpose.

Rails 5.2 has added a feature to configure the job class per mailer.

class CustomNotifier < ApplicationMailer
  self.delivery_job = CustomNotifierDeliveryJob
end

By default, Rails will use the internal DeliveryJob class if the delivery_job configuration is not present in the mailer class.

Now, Rails will use CustomNotifierDeliveryJob for sending emails for CustomNotifier mailer.

CustomNotifier.welcome(User.first).deliver_later

As mentioned above CustomNotifierDeliveryJob can be further configured for logging, exception handling and reporting.

By default, deliver_later will pass following arguments to the perform method of the CustomNotifierDeliveryJob.

  • mailer class name
  • mailer method name
  • mail delivery method
  • original arguments with which the mail is to be sent
class CustomNotifierDeliveryJob < ApplicationJob

  rescue_from StandardError, with: :handle_exception_with_mailer_class

  retry_on CustomNotifierException

  discard_on ActiveJob::DeserializationError

  def perform(mailer, mail_method, delivery_method, *args)
    logger.log "Mail delivery started"
    klass = mailer.constantize
    klass.public_send(mail_method, *args).send(delivery_method)
    logger.log "Mail delivery completed"
  end

  def handle_exception_with_mailer_class(exception)
    if klass = mailer_class
      klass.handle_exception exception
    else
      raise exception
    end
  end
end

We can also simply inherit from the ActionMailer::DeliveryJob and override the retry logic.

class CustomNotifierDeliveryJob < ActionMailer::DeliveryJob
  retry_on CustomNotifierException
end