Since the introduction of Mailer Previews and ActiveJob in Rails 4.1, the framework has truly set itself apart from others in the comprehensiveness and comfort of its workflow around emails. The framework’s creator, DHH, has espoused a perspective that mailers are views, and accordingly Rails now gives us an email workflow that is nearly identical to that around ordinary controllers and views.
I will walk through my complete email stack and workflow, which I have used very effectively across several production apps.
As of Rails 5, the project generator will drop the following ApplicationMailer implementation into your project tree:
# in app/mailers/application_mailer.rb class ApplicationMailer < ActionMailer::Base default from: 'email@example.com' layout 'mailer' end
Perhaps the only notable omission from Rails’ stock email workflow is CSS inlining, but that is easily addressed with the premailer-rails gem, which we will take care of before we go any further:
# in your Gemfile, add premailer-rails with... gem 'premailer-rails'
We should then run
bundle install and restart our server with
bin/rails server. We can now add a mailer.sass stylesheet to the base of our app/assets/stylesheets directory:
// in app/assets/stylesheets/mailer.sass body padding: 0 background: #f7f7f7 text-align: center .email-table width: 538px padding: 20px background: #fff margin: 40px auto text-align: left font-family: 'Helvetica Neue' h1 font-size: 20px p margin-top: 0 font-size: 15px
In order for premailer to pull and inline these styles in production, we will need to add this file to our precompile list:
# add in config/initializers/assets.rb... Rails.application.config.assets.precompile += %w( mailer.css )
Rails’ project generator will drop a mailer layout into app/views/mailers/mailer.html.erb. Now all we have to do to add inline styles to our mailers is to reference this stylesheet from the layout with an ordinary stylesheet_link_tag. With this, premailer will implicitly handle inlining when the message is rendered and delivered.
<!-- in app/views/layouts/mailer.html.erb --> <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <!-- * simply referencing the stylesheet with this stylesheet_link_tag * --> <!-- * is enough for premailer to pull-in and inline your styles * --> <%= stylesheet_link_tag "mailer" %> </head> <body> <%= yield %> </body> </html>
Let’s go ahead and subclass ApplicationMailer now with something concrete. For our purposes here, let’s create a
UserMailer with a
welcome action to deliver a welcome email to new registrants.
# in app/mailers/user_mailer.rb class UserMailer < ApplicationMailer def welcome(user) @user = user mail(to: @user.email, subject: "Welcome to our site!") end end
…and a corresponding template:
<!-- in app/views/user_mailer/welcome.html.erb --> <table class="email-table"> <tbody> <tr class="header-row"> <td> <h1>Welcome, <%= @user.name %>!</h1> </td> </tr> <tr class="body-row"> <td> <p>Thanks for your registering! We appreciate your interest.</p> </td> </tr> </tbody> </table>
Now in order to work with this mailer as we would an ordinary view all we have to do is create a mailer preview for it. We can do this by dropping a file into test/mailers/previews:
# in test/mailers/previews/user_mailer_preview.rb class UserMailerPreview < ActionMailer::Preview def welcome user = OpenStruct.new(email: "firstname.lastname@example.org", name: "John Doe") UserMailer.welcome(user) end end
Note that we are passing an OpenStruct with a few carefully defined attributes to the mailer here. In reality you will probably want to pull an actual record from your database via ActiveRecord or use a factory or fixture.
You can now navigate to http://localhost:3000/rails/mailers, where you will find an index of defined mailer previews, which at the moment will just show the above-defined UserMailer#welcome preview.
…and if you click through the link to “welcome” you will find a preview of the mailer, complete with inlined styles:
You can now work with your mailer and template in-browser as if they were an ordinary controller action and view.
The final piece to Rails’ email workflow worth noting is ActiveJob. As of Rails 4.2 ActionMailer has included an elegant ActiveJob integration in deliver_later. When you wish to deliver an email in your application and would have previously invoked deliver, instead invoke deliver_later and the delivery will be queued up in ActiveJob for asynchronous execution. For example…
That is all there is to it.