Using Mail Interceptors in Rails
Tags: ruby, rails, webdev
Published: 2021-08-19 01:51:34

I stumbled upon a super neat feature of ActionMailer the other night. If you scroll all the way to the bottom of the Rails Guide for ActionMailer you'll find a section on Intercepting and Observing Emails. Intercepting an email allows you to modify your email before sending it. Why would you want to do this you might ask? Well, take the following real life production code in my companies app (slightly modified for confidentiality and brevity):
```ruby
class ApplicationMailer < ActionMailer::Base
  def self.deliver_mail(mail)
    if Rails.env.development? || Rails.env.staging?
        rerouted_email_address = "#{Rails.env}@email.com"
        original_to = mail.header[:to].to_s
        original_subject = mail.header[:subject].to_s

        if mail.cc
          original_cc_emails = mail.cc.dup
          mail.cc = []

          original_cc_emails.each do |email_str|
            other_cc_emails = original_cc_emails.select { |cc| cc != email_str }
            mail.to = rerouted_email_address
            mail.subject = "This is an email with copied folks [cc: #{email_str} +
                             #{other_cc_emails.length} others, to: #{original_to}]"
            super(mail)
          end
        end

        logger.info "Rerouted '#{original_to}' to '#{rerouted_email_address}'."
        mail.subject = "#{original_subject} [originally to: #{original_to}]"
        mail.to = rerouted_email_address
      end
    end

    super(mail)
  end
end
```
That's a lot of code to have in our base mailer that really only runs in some environments. Also, I've found that code like this, where we're actually overriding a core method, makes it hard to debug problems.  It also adds a lot of unneeded complexity when trying to understand what our mailers actually do.

Enter Interceptors.

An interceptor is a special class that has a `delivering_email(mail)` method. The `delivering_email` method is what will be called before the email is actually sent. Inside the method we'll be able to interact and modify our email before it is sent by the original mailer. So simple but so awesome.

I created an `interceptors` directory in `app/mailer` (if you think there's a better place to stash them I'm all ears) and added a `reroute_email_interceptor.rb` file that looks a little something like this:
```ruby
module Interceptors
  class RerouteEmailInterceptor
    def self.delivering_email(mail)
        original_to = mail.header[:to].to_s
        original_subject = mail.header[:subject].to_s

        mail.to = rerouted_email_address
        mail.subject = "#{original_subject} [originally to: #{original_to}]"

        if mail.cc.present?
          original_cc_emails = mail.cc.dup.join(", ")
          mail.cc = []
          mail.subject = "This is an email with copied folks [cc: #{original_cc_emails}, to: #{original_to}]"
        end

        Rails.logger.info "Rerouted '#{original_to}' to '#{rerouted_email_address}'."
      end
    end

    def self.rerouted_email_address
      @rerouted_email_address ||= "#{Rails.env}@email.com"
    end
  end
end
```
Aside from the slight changes to how we handle CC's (which the team is happy with) this will do the same thing as the original code but allows our ApplicationMailer do go back to:
```ruby
class ApplicationMailer < ActionMailer::Base
  
end
```
In order to make the interceptor actually do it's thing we need to add an initializer to add it to ActionMailer::Base
```ruby
# config/initializers/mailer_interceptor.rb
if Rails.env.development? || Rails.env.staging?
 
 ActionMailer::Base.register_interceptor(Interceptors::RerouteEmailInterceptor)
end
```
And we're done! This is a far better, cleaner, and OOP way of handling our development/staging emails.

I can't believe that I've been working in Rails as long as I have and just now learning about Mail Interceptors but I'm sure glad I did!