How to monitor status of actionmailer deliver_later

Viewed 1841

TL;DR

How to keep track of queued mails which are enqueued by deliver_later, along with their delivery state?

Details

I want to send a bunch of emails async using deliver_later from action mailer. It works so far as in mails are sent async and wait: works as expected. So far so good.

I now want to provide some status info about the status of the sent mails. I.e. a simple string like x/y mails sent which I can query via http or whatever later on (which is not a concern of this question!). My expectation was that I can easily access a job queue or something like that where I can then register a callback like in after_action but apparently there is no such thing and it looks like I need to handcraft that using said callbacks but I cannot find a single resource even mentioning the topic 'delivery state'. It appears as if nobody in the world is interested if and when deliver_latered mails are actually sent oO? The closest thing I could found was how to check for this for the purpose of unit tests, but this is definitely not what I want.

1 Answers

There is no datastore (aside from your background job system of choice) that stores information about the completeness or status of your async delivery. However, you can observe the delivery of mail with the Rails Observer pattern. If you'd like to track attempts and status of mail deliveries, I would add a table to your database for each delivery.

Tracking that a mailer method was called to create a delivery:

You could add an before_action to your mailer that writes a database record for the mail message that was called. Give your message a UUID here so you can find it later in the database. The UUID could be added to your message headers.

Tracking that a mailer delivered a message (later):

You can add an observer to your mailer (or all mailers) with register_observer. The Observer class must implement self.delivered_email(message) and that method will be called after an email has been delivered. At that point you could find your database record and write a timestamp on it to mark it as "sent".

Here's an example of what that might look like:

This is untested code, it may not work exactly as written

# your mailer class
class NotifierMailer < ApplicationMailer
  after_action :record_delivery

  private

  def record_delivery
    # create an identifier for this mail message and add it 
    # to the headers of the mail message
    headers['X-Delivery-Id'] = SecureRandom.uuid
    # write a database record to track that this email is to be sent
    MailDelivery.create(uuid: headers['X-Delivery-Id'])
  end
end

# an observer class
class MailDeliveryObserver
  def self.delivered_email(message)
    delivery = MailDelivery.find_by(uuid: message.headers['X-Delivery-Id'])
    delivery&.touch(:sent_at)
  end
end

# in application.rb
# NotifierMailer can be any mailer
NotifierMailer.register_observer(MailDeliveryObserver)

Here's an article that explains a little more about observers

And some current Rails docs on observers

Related