Rails: how to write custom email interceptors
Nowadays it is fairly easy to intercept and modify mails globally before they are sent. All you have to do is register an interceptor class which responds to .delivering_email(message)
. This card will show you two common use cases.
Subject prefix:
Usually you want to prefix the subject line of emails with the current environment (except production) so you can differentiate between production mails and mails from other environments. Of course a prefix can be set directly in the mailer, however, it is safer and cleaner to do this with an interceptor as you don't have to modify the subject line for each new mailer method.
Copyclass SubjectPrefixInterceptor def self.delivering_email(message) message.subject = "[#{Rails.env}] #{message.subject}" end end unless Rails.env.production? ActionMailer::Base.register_interceptor(SubjectPrefixInterceptor) end
Define it in an initializer like config/initializers/mail_subject_prefixing.rb
.
Whitelisted email addresses:
Sometimes you might want to try sending mails from staging but just to selected recipients. This example will show you how to define a email whitelist for staging using a mail interceptor.
Copyclass Whitelist def initialize(config) if config.empty? @active = false else @active = true @config = config end end def active? @active end def fallback_email @fallback_email ||= @config.fetch('fallback_email') end def allowed @allowed ||= @config.fetch('whitelist') end end class WhitelistConfig module ClassMethods def for(environment) @whitelists ||= {} @whitelists[environment] ||= Whitelist.new(config.fetch(environment, {})) end private def config @config ||= YAML.load_file(Rails.root.join('config/mail_whitelist.yml')).freeze end end extend ClassMethods end class WhitelistInterceptor def self.delivering_email(message) whitelist = WhitelistConfig.for(Rails.env) if whitelist.active? unless (message.to - whitelist.allowed).empty? message.subject = "#{message.subject} [#{message.to}]" message.to = whitelist.fallback_email end end end end ActionMailer::Base.register_interceptor(WhitelistInterceptor)
Your whitelist config should be defined in config/mail_whitelist/staging.yml
like this:
Copystaging: fallback_email: fallback@example.com whitelist: - personal1@example.com - personal2@example.com
A spec could look something like this:
Copydescribe WhitelistInterceptor do describe '.delivering_email' do def send_email(**options) ActionMailer::Base.mail(from: 'app@example.com', subject: 'My Subject', body: 'My Message', **options).deliver_now end context 'on staging' do before { allow(Rails).to receive(:env).and_return('staging'.inquiry) } it 'redirects e-mails that would be sent to public recipients' do email = send_email(to: 'not-whitelisted@example.com') expect(email.to).to contain_exactly('fallback@example.com') expect(email.subject).to eq('My Subject [not-whitelisted@example.com]') end it 'allows sending e-mails to whitelisted recipients' do email = send_email(to: 'personal1@example.com') expect(email.to).to contain_exactly('personal1@example.com') expect(email.subject).to eq('My Subject') end end context 'on production' do before { allow(Rails).to receive(:env).and_return('production'.inquiry) } it 'does not redirect e-mails' do email = send_email(to: 'not-whitelisted@example.com') expect(email.to).to contain_exactly('not-whitelisted@example.com') expect(email.subject).to eq('My Subject') end end end end
Related:
You might also be interested in gems for mail interception:
Your development team has a full backlog of feature requests, chores and refactoring coupled with deadlines? We are familiar with that. With our "DevOps as a Service" offering, we support developer teams with infrastructure and operations expertise.