Flash outgoing mail in Ruby on Rails development

Written . Tagged Ruby, Ruby on Rails.

[Screenshot]

In Ruby on Rails web apps, you can use ActionMailer to send mail (and process incoming mail). This is useful for e.g. account confirmation and password recovery.

When developing the application, you might not want any mails to actually be sent. This can be configured in config/environments/development.rb. The default development configuration of ActionMailer is to actually send mails, but to ignore delivery errors. It’s easy to make it not send anything:

1
config.action_mailer.delivery_method = :test

With this setting, outgoing mail will just be appended to the ActionMailer::Base.deliveries array. That’s all well and good, but it’d be nice to see the contents of those mails as they’re appended. This turned out to be a relatively simple matter.

Make the

1
config.action_mailer.delivery_method = :test

setting in config/environments/development.rb as mentioned above. In the same file, also

1
require "flash_mail"

Add flash_mail.rb to the lib directory:

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
34
35
36
37
38
39
40
41
# By Henrik Nyh <http://henrik.nyh.se> 2007-06-29
# http://henrik.nyh.se/2007/06/flash-outgoing-mail-in-ruby-on-rails-development
# Free to modify and redistribute with due credit.

module FlashMail
  module ControllerTracking

    def self.included(action_controller)
      action_controller.instance_eval do
        before_filter  :set_current_controller
        cattr_accessor :current_controller
      end
    end

  protected

    def set_current_controller
      ApplicationController.current_controller = self
    end

  end
  module Delivery

    def self.included(action_mailer)
      action_mailer.instance_eval do
        alias_method_chain :deliver!, :flash
      end
    end

    def deliver_with_flash!(*args)
      deliver_without_flash!(*args)
      if controller = ApplicationController.current_controller
        controller.instance_eval { flash[:mail] = ActionMailer::Base.deliveries.last }
      end
    end

  end
end

ActionMailer::Base.send     :include, FlashMail::Delivery
ActionController::Base.send :include, FlashMail::ControllerTracking

This extension does a few things:

The ApplicationController class gets a current_controller accessor. This accessor is used to store the current controller object in a before filter. The point of this is to give ActionMailer a handle on the current controller.

The ActionMailer deliver! method is wrapped to make the current controller set flash[:mail] to the last mail – the one just pushed onto the array.

That’s pretty much it. Restart the server to have it pick up on the added extension. Make your views output flash[:mail] if it exists, and style to taste. I do

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<% f = flash.to_hash  # A real hash is easier to manipulate without deprecation nags, and should be safe enough %>
<% if f[:mail] %>
  <div id="mail_flash">
    <h4>Mail that would have been sent:</h4>
    <pre><%= auto_link(h(f.delete(:mail))) %></pre>
    <p><%= link_to_function("Discard", "mf = document.getElementById('mail_flash'); mf.parentNode.removeChild(mf)") %></p>

  </div>
<% end %>
<% unless f.empty? %>
  <ul id="messages">
    <% f.each do |kind, msg| %>
      <li class="<%= kind %>"><%= msg %></li>
    <% end %>
  </ul>
<% end %>

and

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#mail_flash {
  background: #FCF2B8;
  -moz-border-radius: 10px;
  padding:1.5em;
  margin-bottom:1em;
}

#mail_flash pre {
  margin: 1.5em 0;
}

#mail_flash p {
  font-weight: bold;
}

.