Last month, Thoughtbot’s Matt Jankowski wrote up a post about How to not accidentally send thousands of ‘beta invites’.

The method outlined is elegant and simple. It also requires modifying the recipients of all of your ActionMailer models.

We wanted something that would allow our QA team to safely test email-sending features all over the site, and without a lot of management overhead.

module ActionMailer
  class Part
    def to_mail_with_capture defaults
      self.body += defaults.captures
      to_mail_without_capture defaults
    end
    alias_method_chain :to_mail, :capture
  end
 
  class Base
 
    cattr_writer :captured_email_recipient_address
 
    def captured_email_recipient_address
      @@captured_email_recipient_address
    end
 
    def captures
      # Add information about captured recipients to the footer of the email body
      captured_addresses = @captures.map do |method, addresses|
 
        if addresses.kind_of?(Array)
          captured_address = [
            "<ul>",
            addresses.map { |a| "<li>#{a}</li>" },
            "</ul>"
          ].join
        else
          captured_address = addresses
        end
 
        [
          "<dl>",
            "<dt>", method, "</dt>",
            "<dd>", captured_address, "</dd>",
          "</dl>"
        ].join
      end
 
      captured_addresses_report = [
        "<div><p>Intended recipients</p>",
        captured_addresses,
        "</div>"
      ].join
 
      captured_addresses_report
    end
 
    def captured method, list
      @captures ||= {}
      @captures[method] = list
      self.recipients_without_capture = captured_email_recipient_address
    end
 
    def recipients_with_capture= list
      captured :recipients, list
    end
    def cc_with_capture= list
      captured :cc, list
    end
    def bcc_with_capture= list
      captured :bcc, list
    end
 
    # Defer to the overridden setters if setting the list of recipients
    # Otherwise, return the unaliased list
    def recipients_with_capture list = nil
      list ? self.recipients_with_capture= list : recipients_without_capture
    end
    def cc_with_capture list = nil
      list ? self.cc_with_capture= list : cc_without_capture
    end
    def bcc_with_capture list = nil
      list ? self.bcc_with_capture= list : bcc_without_capture
    end
 
    alias_method_chain :cc=, :capture
    alias_method_chain :bcc=, :capture
    alias_method_chain :recipients=, :capture
    alias_method_chain :cc, :capture
    alias_method_chain :bcc, :capture
    alias_method_chain :recipients, :capture
 
  end
end

Requiring this file will cause all ActionMailer deliveries to be sent to one address, which you can set by using:

ActionMailer::Base.captured_email_recipient_address = "testing@yourdomain.com"

Additionally, a div will be appended to the email body indicating who its intended recipients were, and how the email would have been sent to them; recipient, cc, or bcc. Perfect for QA!

We simply set up the testing address as a mailing list in Google Docs, so we don’t have to manage who gets these emails from within the application, and can instead use Google’s tools.

Remember to set in your deploy recipe whether or not emails should be captured, as you wouldn’t want to accidentally capture all the email being sent out from your live application!

Leave a Comment

Enclose code in <code lang="ruby"></code> if you care.
Preview your comment using the button below.