gap intelligence has several applications that help us deliver GFD (Great Freakin’ Data) to our clients via email. Some applications send emails on demand and others send based on customers’ scheduled preferences. Our services are built using Ruby on Rails which uses Action Mailer to send emails, and we send out thousands of emails every week (THOUSANDS!). For the most efficient development, Rails testing environments should be as close to production as they can be. And we need to ensure that we don’t accidentally send unneeded emails to real clients during the development or QA. Action Mailer provides hooks into the Mail interceptor methods and I wanted to share how we are leveraging the Action Mailer lifecycle to prevent sending unwanted emails.

nimble.com

Create and Register

Ruby on Rails Guides lists only 2 requirements for our Mail Interceptor class to work: 1) class must implement the method delivering_email(message); 2) Action Mailer framework needs to register our class. We are going to create the class MailerInterceptor and add the corresponding initializer:

# app/mailers/mailers_interceptor.rb

class MailerInterceptor
  def self.delivering_email(message)
    # customization
  end
end
# config/initializers/mailers_interceptor.rb
...
ActionMailer::Base.register_interceptor(MailerInterceptor)

Enable

We definitely want a production environment to send emails as usual and intercept only when we run an application in another Rails environment. There are several ways to do that:

  1. add a condition in an initializer file
    # config/initializers/mailers_interceptor.rb
    
    if Rails.env.qa?
      ActionMailer::Base.register_interceptor(MailerInterceptor)
    end
    
  2. register interceptor in the corresponding environment file
    # config/environments/qa.rb
    
    ActionMailer::Base.register_interceptor(MailerInterceptor)
    
  3. add a condition in interceptor class itself
    # app/mailers/mailers_interceptor.rb
    
    class MailerInterceptor
      def self.delivering_email(message) 
        if intercept?
          # customization
        end
      end
    
      def self.intercept?
        Rails.env.qa?
      end
    end
    

I personally prefer the last option since interceptor class will be a single place for not only the logic of email customization but also a logic of “when” to do interception itself, what’s more, a potential place for “how” to intercept for certain environments. Here is a more complex example where we need to enable the interceptor for more than one environment:

  • development: should email only to my personal email (could be defined as ENV variable on the local machine)
  • QA: should email only to the development team members (group email or a list of individual emails)
  • staging: should email to any email address within our company, but never to real clients

In that case, it would be easier to have a single place where we keep track of that all, like in the example below

# app/mailers/mailers_interceptor.rb

class MailerInterceptor
  def self.delivering_email(message) 
    intercept_development(message) if Rails.env.development?
    intercept_qa(message)          if Rails.env.qa?
    intercept_staging(message)     if Rails.env.staging?
  end

  def self.intercept_development(message) 
    # customization for development
  end

  def self.intercept_qa(message) 
    # customization for QA
  end

  def self.intercept_staging(message) 
    # customization for staging
  end

end

Prevent

With the interceptor created, registered, and enabled for the QA environment, now let’s actually add protection for sending unwanted emails. The message object that is passed to the method delivering_email is the actual email message that we can modify before it hits the delivery agent. For example, we can filter GFD (Great Freaking Data) recipients to only email addresses within our corporate email in the staging environment and send everything to the development team group email address in QA environment:

# app/mailers/mailers_interceptor.rb

class MailerInterceptor  
  def self.delivering_email(message) 
    intercept_staging(message) if Rails.env.staging? 
    intercept_qa(message)      if Rails.env.qa? 
  end

  def self.intercept_qa(message) 
    message.to = [ ENV['TEAM_GROUP_EMAIL_ADDRESS'] ] # could be defined using dotenv
  end 

  def self.intercept_staging(message) 
    message.subject.prepend(staging_subject_prefix)
    message.to = filter_recipients(message.to)
  end 

  def self.staging_subject_prefix 
    'IGNORE, EMAIL FROM STAGING: '
  end 

  def self.corporate_domain
    '@gapintelligence.com'
  end 

  def self.filter_recipients(recipients)
    recipients.select { |recipient| recipient.ends_with? corporate_domain }
  end
end

Interceptor allows making any modifications to mail objects: change the subject, add cc and bcc , even modify from. It will apply these changes for the mail delivery life cycle of every email sent before it will be actually handed off to the delivery agents. This allows developers not to worry about who sends a message – own code in the application or authentication gem that sends emails during registration. Email messages will only go where they need to.

Do you have what it take to be a gapper? Our Product Development team is hiring. Head to our culture section to learn more about open positions. We’re conducting phone interviews as we work towards flattening the curve. Stay safe and healthy. We’re all in this together.