Basically every web application sends emails these days, and since we do not live in the 1980s anymore, users expect them to be colorful and well-designed. So we need to provide two templates in Rails: a plain-text version and a HTML version (more details below). Here is a quick example using the on-board ActionMailer:
# app/views/my_mailer/welcome_email.text.erb
Welcome to the party!
--- --- --- --- --- --- --- --- --- --- --- ---
Here some data:
Location | Mood
Berlin | 😀
L.A. | 🍩
# app/views/my_mailer/welcome_email.html.erb
<h1>Welcome to the party!</h1>
<hr>
Here some data:
<table>
<tr><td>Location</td><td>Mood</td></tr>
<tr><td>Berlin</td><td>😀</td></tr>
<tr><td>Stockholm</td><td>😎</td></tr>
</table>
Considering that most email clients support HTML and most people prefer it, it seems excessive to provide both versions. However, only sending HTML does not seem right and might even lead to delivery problems. So, we try to derive the plain-text version from our HTML template. Let’s look at the mailer:
class MyMailer < ApplicationMailer
def welcome_email
mail(subject: "Welcome") do |format|
format.html
format.text { render_text_for(__method__) }
end
end
end
We leave our HTML version in place but remove the text version. In the welcome_email
method, we let Rails run its natural rendering for the HTML version. However, for the text version, we call the method render_text_for
and pass in the current method’s name. This is necessary, so the converter can infer the correct template name later. We implement the method in the base class:
class ApplicationMailer < ActionMailer::Base
using Refinements::Mailings
protected
def render_text_for(template_name)
text = render_to_string(template_name, formats: :html).html_to_plain
render(plain: text)
end
end
So we just render the HTML version and call html_to_plain
on it. This is the actual converter, which we define in a refinement:
module Refinements::Mailings
refine String do
def html_to_plain
preprocessed = self
.gsub("<hr>", "\n--- --- --- --- --- --- --- --- --- --- --- ---\n")
.gsub(/<br\\?>/, " ") # line breaks
.gsub(/<li[^>]*>/, "- ") # lists
.gsub(/<\/t[hd]>\s*<t[hd][^>]*>/, " | ") # table cells (tr and table tags are removed later)
return ActionController::Base.helpers.strip_tags(preprocessed)
.split("\n").map(&:strip).join("\n") # fix indentation
.gsub("\n\n\n", "\n") # remove extensive new lines
.strip
end
end
end
The converter is pretty basic, but we have tried it on hundreds of emails, and it works pretty well. Sometimes, we adjust the newlines in the HTML template a little, and then it is perfect. However, please feel free to customize the replacements to your needs.
This approach is pretty basic, but it works well in most cases. It removes duplication and the risk of forgetting to adjust one of the templates. And it saves a little work. If the templates become overly complex, we can still fall back on providing both versions explicitly.
Happy coding!
Further Reading
- Stackoverflow Question - How to convert to plain text?
- Html2Text Gem - Alternative to the html_to_plain method
- Premailer Gem - More extensive email styling tool