Wednesday, February 11, 2015

Rails 4.x and Mail Gem Message Interceptors vs. Custom Delivery Methods

Currently in mailer you can write an interceptor (e.g. as an initializer) like:

  class MyInterceptor
    def self.delivering_email(message)
      # ... would do something with every email here, even if we don't actually send
    rescue => e
      # ... error handling code that tries to ensure an error won't be raised if you mail errors
    end
  end

ActionMailer::Base.register_interceptor(MyInterceptor)

and then if you wanted to only do the action with the message object in MyInterceptor but didn't want to actually send and you only want to not send in certain environments (like development, etc.), then in your Rails config you could do:

  config.action_mailer.raise_delivery_errors = false
  config.action_mailer.smtp_settings = {
      :address => nil
  }

But, that's a little clunky.

Instead, you can use a custom delivery method as described here and alluded to in Rails documentation, and currently barely mentioned in the Mail gem.

And if you wish to still send via SMTP, though you could create a custom delivery method class to extend ::Mail::SMTP or create a ::Mail::SMTP instance and use it in your deliver! method, to allow ActionMailer's smtp_settings to continue to work, patching ::Mail::SMTP is a good option.

For example:

(Updated 2015-04-02)

In lib/my_mail_delivery_methods.rb:

require 'mail'

# these must be loaded prior to application configuration, but are too bulky to put into application.rb
# in anything but a require.

module AbleToDoSomethingToMail
  def do_something(message)
    # do something here.
    # even though is in lib, still has access to models, etc.
  rescue => e
    begin
      # do something on recovery here.
      # be careful about anything we do on recovery so we don't send email on error, if you have that set-up
    rescue => e2
      puts "#{e.message}\n#{e.backtrace.join("\n")}"
    end
  end
end

class PersistMailToDatabaseOnly
  include AbleToDoSomethingToMail

  def initialize(values)
  end

  def deliver!(mail)
    do_something(mail)
  end
end

class ::Mail::SMTP
  include AbleToDoSomethingToMail

  alias_method :orig_deliver_copied_to_do_something, :deliver!

  def deliver!(mail)
    do_something(mail)
    orig_deliver_copied_to_do_something(mail)
  end
end

Then in config/environments/development.rb, etc.:

  # ActionMailer settings
  config.action_mailer.perform_deliveries = true
  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.delivery_method = DoSomethingToMailWithoutSend

And in config/environments/production.rb:

  # ActionMailer settings
  config.action_mailer.perform_deliveries = true
  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.delivery_method =:smtp
  config.action_mailer.smtp_settings = {
      :address => "your.mail.host"
  }

No comments: