Nebojša Stričević wrote this on April 27, 2012

Filtering emails on staging

On one of our projects we’re using a copy of the production site database on the staging server. Since the application sends several different notifications to site users, one of the problems with this setup on the staging server is that during testing an email can accidentally be sent to a site user.

Older solution for this problem was to change all email addresses of site users to an address that belongs to our company domain. Change was executed manually after fresh database from production server was deployed to the staging.

As our database grew so did time needed to execute this task, to the point where we wished to skip executing this task. Another problem was that several new developers were added to the project and risk for simply forgetting to execute this task grew also.

Our solution to this problem was to introduce email interceptors on the staging server which would intercept each email that is to be sent, check it’s destination and stop sending if email does not belong to one of our developers. If email belongs to the company domain, interceptor would allow sending, so we can easily test application notifications.

This is how one email interceptor might look like:

# app/interceptor/staging_mail_interceptor.rb

class StagingMailInterceptor

  def self.delivering_email(message)
    message.to = extract_allowed_recepients(message)

    message.perform_deliveries = false if message.to.empty?
  end

  private

  def self.extract_allowed_recepients(message)
    message.to.select { |address| allowed_address?(address) }
  end

  def self.allowed_address?(address)
    allowed_domains = App.allowed_email_domains

    matches_allowed = allowed_domains.count { |domain| address.include?(domain) }

    matches_allowed != 0
  end

end

Each email message can be sent to multiple email addresses, so email interceptor removes email addresses that don’t belong to domains listed in application configuration:

# config/environments/staging.rb
# config/environments/test.rb

App.configure do
  config.allowed_email_domains = ['@renderedtext.com', '@clientdomain.com', '@someotherdomain.com']
end

Email interceptor should be active only on staging server:

# config/initializers/mailers_config.rb

require 'staging_mail_interceptor'

if Rails.env.staging?
  ActionMailer::Base.register_interceptor(StagingMailInterceptor)
end

This way, an email interceptor will not interfere with email delivery on production or development environment.

Writing a test for this kind of set up requires small dose of gymnastics. Email interceptors are active only when the application is running in staging environment. If you set interceptor active in the test environment also, this will interfere with other tests whose subject is email delivery system.

A solution to this problem is to register the interceptor only during the test which tests email intercepting feature:

Mail.register_interceptor(StagingMailInterceptor)

To remove email interceptor after test is done, you can do following:

Mail.class_variable_get(:@@delivery_interceptors).delete(StagingMailInterceptor)

This setup allows us to think less about customer emails when performing testing and setting up testing environment, which is done more frequently now since data model is under overhaul. It’s easy to add new domains if more people outside the company are working on application testing. And there is no time overhead when setting up staging environment or sending email on production site.

comments powered by Disqus


Suggested Reads

Rails Testing Handbook

A new ebook on building test-driven Rails apps with RSpec and Cucumber.

At Rendered Text, we have a long history with Ruby on Rails. Checking the blog archive reminds me that we published first posts about working with Rails way back in 2009.

———

Rendered Text is a software company. For questions regarding Semaphore, please visit semaphoreci.com. Otherwise, feel free to get in touch any time by sending us an email.