"Applying HTML styles to an email" is one of the tasks that makes frontend developers grin. If you are not too experienced at frontend, it can be a bit of a hell. Luckily for us Rails developers, Roadie helps a lot.

The problems

To put it simply, the problem when applying styles to an email is that you can't use an external CSS file as you would normally do. So you have to use inline CSS styling, which is not only very ugly code to read (as if it weren't enough!), but it is harder to maintain.

To add a bit more pain, each email application has a different way of rendering emails, so, in the same way you have to do "hacks" to get something working properly on Internet Explorer, you need to do some "hacks" to get your emails seen properly on Gmail, Thunderbird, Lotus Notes, or your Android smartphone email app.

The solutions

For the inline CSS problem, we use the roadie gem. Basically, it takes your email template, the CSS for the email you've defined in a separate file, and injects the CSS on the email HTML before the email gets sent. Roadie 3.x is framework agnostic, and you can use it in combination with roadie-rails to use it seamlessly in your Rails projects. Anyway, I had some trouble setting it up and ended up using Roadie 2.4, which is for Rails only (sorry!) and proved to work very well in another client's application.

To deal with email viewer related problems, HTML Email Boiler Plate is a great head start. It will provide you with an email template and CSS that will save you a lot of headaches, as it covers a lot of common problems.

Getting it done, step by step (kinda)

The first thing would be getting the email layout in place, so:

  1. Install roadie. If you use version 2.4.x, you'll need zero additional configuration (SO good!). If using version 3.x you'll need to look the docs to get it working.
  2. Download the Email Boilerplate from the site linked above.
  3. Separate HTML (I've put it into HAML for readability) from the CSS. Put the HAML file in your app/views/layouts folder and the CSS on your app/assets/stylesheets folder.
  4. Add email.css to the asset precompilation line on your application.rb.
  5. Tell your mailer, or some of your emails methods, to use the layout you just created. This is very easy and explained on the Rails Guides
  6. Get your layout to yield for the contents in your email views. As you will be working with tables, it could be a good a idea to have the main table in the layout, and the rows corresponding for the header and the footer in the layout as well. In the gist linked before, you can see an example, but it will vary depending on your template.
  7. Adapt your mailer views to work as (or within) a table.
  8. From this moment on, it's just a matter of seeing the resulting email, and fixing the styling in your CSS file, until it looks neat. Note that many attributes stablished on the tables in the layout can be extracted to the CSS file, but be careful. Have some reference on table HTML at hand just in case (W3C and Stackoverflow are your friends),

Helpers for your email views

When on step 7, you'll probably have to integrate a table structure in your email views. I'd recommend you to create an email_helper to abstract as much as possible the table structure into methods. So if you have to put the email text header into something like this:

%tr#mail-subject
  %td
    = image_tag "happy-face-small.png" # this varies in each email
  %td
    = "Hello #{@user.name}!" # this varies for each email

Instead of repeating the row-cell structure in each email view, create a helper that does:

module EmailHelper
  def email_header_with_icon(icon, title)
    content_tag :tr, id: "mail-subject" do
      content_tag(:td, image_tag("#{icon}-small.png")) +
      content_tag(:td, title)
    end
  end
end

And in your view, use this new helper to keep things clean and readable (and why not, testable!):

= email_header_with_icon("happy-face", "Hello #{@user.name}!")

If you are creating a common email helper instead of one for each individual mailer, don't forget to add helper 'email_helper_name' just below your layout line in your mailer!

Previewing your emails

We normally use the letter_opener gem, which works very well, although you'll have to send your emails manually to get them shown in the browser, which is a bit slower.

But, if you are using Rails 4.1, then you are a lucky person! Rails 4.1 has a Mail Previewer feature that will make your email testing and previewing a whole lot easier. Check it out and start using it today if you can!

Anyway, if you use this, I've noted that it's not friendly to the curly syntax in HAML, so, in your layout, things like this:

%table{align: "center", border: "0", cellpadding: "0", cellspacing: "0"}

Should become:

%table(align="center" border="0" cellpadding="0" cellspacing="0")

Nothing some find + replace can't solve, anyway ;)

Devise and HTML emails

If you are using Devise, probably you have already used rails generate devise:views to have your Devise views and mailer views available for editing. But now that you have some sexy styling in your emails, you'll think that the signup emails should get some love too! How can we do this? Add this on your application.rb inside your application class declaration and then restart your browser:

config.to_prepare do
  Devise::Mailer.layout 'email' #specify the layout
  Devise::Mailer.helper 'email' #only if needed!
end

Now Devise will use your layout in the emails. Remember to edit the mailer views as you did with yours!

If you want to preview the emails with the Rails 4.1 previewer, create a previewer like this:

class Devise::MailerPreview < ActionMailer::Preview
  def confirmation_instructions
    Devise::Mailer.confirmation_instructions(User.first, {})
  end

  def reset_password_instructions
    Devise::Mailer.reset_password_instructions(User.first, {})
  end
end

As a note, one thing I've noticed is that once you add the Devise mailer to your previews, every time you modify a previewer or a mailer method, the previewer will fail to show them and you'll need to restart your local server in order to see the changes you've made.

Conclusion

I hope this helps some fellow devs in the future and makes someone life easier. Any comments or suggestions? Don't hesitate!

Picture "The Roadie" by Gareth Harfoot on Flickr