Abort mail delivery with Rails 3 interceptors

Written . Tagged Action Mailer, Ruby on Rails.

It can be a good idea to use anonymized production data in development and staging. You might change every user email to dev+user123@example.com, say.

It’s unnecessary to send real mail to these users, but you might not want to deactivate sending all mail. It’s nice if things like the signup flow work, including mail delivery.

You can use Rails 3 interceptors for this. Every mail is passed to the interceptor before delivery. The interceptor can modify the Mail::Message instance. As discussed here, interceptors don’t let you abort delivery in an obvious way, but you can achieve it by setting message.perform_deliveries = false.

Note that if you use something like Resque::Mailer, the mail will still be enqueued; delivery isn’t aborted until the job is processed.

Example

Register the interceptor:

config/initializers/action_mailer.rb
1
ActionMailer::Base.register_interceptor(PreventMailInterceptor)

Define the interceptor, making sure to modify the conditions to suit your needs:

lib/prevent_mail_interceptor.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class PreventMailInterceptor
  RE = /dev\+.*@example\.com/

  def self.delivering_email(message)
    unless deliver?(message)
      message.perform_deliveries = false
      Rails.logger.debug "Interceptor prevented sending mail #{message.inspect}!"
    end
  end

  def self.deliver?(message)
    message.to.any? { |recipient| recipient !~ RE }
  end
end

And spec it:

spec/lib/prevent_mail_interceptor_spec.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
require 'spec_helper'

describe PreventMailInterceptor, "delivery interception" do
  it "prevents mailing some recipients" do
    PreventMailInterceptor.stub(:deliver? => false)
    expect {
      deliver_mail
    }.not_to change(ActionMailer::Base.deliveries, :count)
  end

  it "allows mailing other recipients" do
    PreventMailInterceptor.stub(:deliver? => true)
    expect {
      deliver_mail
    }.to change(ActionMailer::Base.deliveries, :count)
  end

  def deliver_mail
    ActionMailer::Base.mail(to: "a@foo.com", from: "b@foo.com").deliver
  end
end

describe PreventMailInterceptor, ".deliver?" do
  it "is false for recipients like dev+*@example.com" do
    message = mock(to: %w[dev+123@example.com])
    PreventMailInterceptor.deliver?(message).should be_false
  end

  it "is true for other recipients" do
    message = mock(to: %w[user@example.com])
    PreventMailInterceptor.deliver?(message).should be_true
  end
end

Done!