Using a Rails' Mail Interceptor to Test Email

August 07, 2015

I’m currently setting up a large number of emails as part of our Rails application. I like to run them through Email on Acid to test them on as many email clients as possible. However, this presents a problem in development as the images are all hosted locally. The third party testing service can’t access that without jumping through hoops to setup reverse proxies, etc.

Up until now I’ve deployed to a special staging server and then send the emails from there. This works, but it doesn’t make for a rapid development process.

I don’t actually care what the images look like while testing, just that they are present and in the right size so I can test spacing and layout.

So I created a mail interceptor to replace all of the images in the email with publicly accessible versions from Placehold.it.

Here is an example of what it looks like: Example

Here is the interceptor class. A couple of notes:

  • Nothing happens unless the email is sent to the testing service.
  • It required the Nokogiri gem.
  • I wrap everything in a block without catching errors as I’d prefer broken image links to emails not sending.
  • I shell out to Imagemagick’s identify command. There are some image sizing gems I could use, but this is only used in development and a bit hacky anyway so…
  • Change the recipient to match your specific email address used for testing.

lib/email_on_acid_interceptor.rb

class EmailOnAcidInterceptor
  def self.delivering_email(message)

    return unless message.to_addrs.include?('xxxx.runme@previews.emailonacid.com')

    doc = Nokogiri::HTML(message.html_part.body.to_s)

    doc.css('img').each do |img| 
      begin
        asset_path = URI.parse(img['src']).path
        fingerprint = asset_path[/-([0-9a-f]{7,128})\.[^.]+\z/, 1]
        asset_path.sub!("-#{fingerprint}", '')
        asset_path.sub!("/assets/", '')
        file_name = Rails.application.assets[asset_path].filename
        ext = File.extname(file_name)
        w, h = `identify -format '%w %h' #{file_name.shellescape}[0]`.split(' ')
        img['src'] = "http://placehold.it/#{w}x#{h}#{ext}"
      rescue
        true
      end
    end

    message.html_part.body = doc.to_s
  end
end

To make this work it needs to be initialized. The choice of filename is up to you, but this seems like a reasonable solution. The important thing is to only load the interceptor in the development environment.

config/initializers/mail_interceptors.rb

if Rails.env.development?
  ActionMailer::Base.register_interceptor(::EmailOnAcidInterceptor)
end

That’s it. Restart your rails server and try it out.