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.
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:
- add a condition in an initializer file
# config/initializers/mailers_interceptor.rb if Rails.env.qa? ActionMailer::Base.register_interceptor(MailerInterceptor) end
- register interceptor in the corresponding environment file
# config/environments/qa.rb ActionMailer::Base.register_interceptor(MailerInterceptor)
- 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.