Muddle
Email clients are not web browsers. They render html all funny, to put it politely. In general, the best practices for writing HTML that will look good in an email are the exact inverse from those that you should use for a web page. Remembering all those differences sucks.
With muddle, we're trying to make it so that the only thing you have to know is to use tables in your emails. Muddle will take care of the rest. It uses ideas from HTML Email Boilerplate to help you get your emails in line without having to know tons about how clients render it.
- CSS will be inlined using premailer, so you can use external style sheets as you normally would.
- HTML elements will be augmented with all the attributes they need for email,
so you don't need to worry about ensuring all your anchor tags have
_target
set, etc. - The resulting html document will be checked for tags that don't play well in
email (like
div
).
Installation
Add this line to your application's Gemfile
:
gem 'muddle'
And then execute:
$ bundle
Or install it yourself with:
$ gem install muddle
Usage
The Basics
However you're sending email, you'll want to get what you intend to be the html body of your email into a variable. How you do that is up to you. Say you have a SLIM template and you're using Mail to build and send your emails:
require 'muddle'
require 'slim'
require 'mail'
body = Slim::Template('path/to/welcome_email.html.slim').render
# This is really the only thing Muddle is involved in.
muddled_body = Muddle.parse(body)
email = Mail.new do
to '[email protected]'
from 'welcome@awesome_web_service.com'
subject 'Welcome!!!!!!!!!!1!!!!one!!!'
html_part do
body muddled_body
content_type 'text/html; charset=UTF-8'
end
end
If you're using ActionMailer
, you could do like this:
class UserMailer < ActionMailer::Base
def welcome_email
mail(
to: '[email protected]',
from: 'welcome@awesome_web_service.com',
subject: 'Welcome!!!!!!!!!!1!!!!one!!!'
) do |format|
format.html { Muddle.parse(render) }
end
end
end
Configuration
You can configure Muddle with a block. Maybe throw this in an initializer of some sort. Here are all the defaults:
Muddle.configure do |config|
config.parse_with_premailer = true
config.insert_boilerplate_styles = true
config.insert_boilerplate_css = true
config.insert_boilerplate_attributes = true
config.validate_html = true
config.generate_plain_text = false
config.logger = nil
config. = {
:remove_comments => true,
:with_html_string => true,
:adapter => :hpricot
}
end
Writing An Email
For best results, just start writing your email with a table tag and move in
from there. Muddler will handle putting the xmlns
and DOCTYPE
and a bunch
of stuff into the <head>
, then open the <body>
for you. It will also close
these tags at the end.
For example, if you have a template the ends up like this:
<html>
<body>
<table>
<tbody>
<tr>
<td><h1>Welcome to our AWESOME NEW WEB SERVICE!</h1></td>
</tr>
<tr>
<td><p>You should come <a href="http://awesome_web_service.com">check us out</a>.</p></td>
</tr>
</tbody>
</table>
</body>
</html>
Muddle will spit out this:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body style="width: 100% !important; margin: 0; padding: 0; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%;">
<table cellpadding="0" cellspacing="0" border="0" align="center">
<tbody>
<tr>
<td valign="top"><h1 style="color: black !important;">Welcome to our AWESOME NEW WEB SERVICE!</h1></td>
</tr>
<tr>
<td valign="top"><p style="margin: 1em 0;">You should <a href="http://awesome_web_service.com" style="color: blue;" target="_blank">check us out</a>.</p></td>
</tr>
</tbody>
</table>
<style type="text/css">
/* Boilerplate CSS for BODY */
#outlook a {padding:0;}
#backgroundTable {margin:0; padding:0; width:100% !important; line-height: 100% !important;}
.ExternalClass {width:100%;}
.ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div {line-height: 100%;}
.image_fix {display:block;}
h1 a:active, h2 a:active, h3 a:active, h4 a:active, h5 a:active, h6 a:active {color: red !important;}
h1 a:visited, h2 a:visited, h3 a:visited, h4 a:visited, h5 a:visited, h6 a:visited {color: purple !important;}
@media only screen and (max-device-width: 480px) {
a[href^="tel"], a[href^="sms"] {
text-decoration: none;
color: blue;
pointer-events: none;
cursor: default;
}
.mobile_link a[href^="tel"], .mobile_link a[href^="sms"] {
text-decoration: default;
color: orange !important;
pointer-events: auto;
cursor: default;
}
}
@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) {
a[href^="tel"], a[href^="sms"] {
text-decoration: none;
color: blue;
pointer-events: none;
cursor: default;
}
.mobile_link a[href^="tel"], .mobile_link a[href^="sms"] {
text-decoration: default;
color: orange !important;
pointer-events: auto;
cursor: default;
}
}
</style>
<style type="text/css">
body { width: 100% !important; -webkit-text-size-adjust: 100% !important; -ms-text-size-adjust: 100% !important; margin: 0 !important; padding: 0 !important; }
img { outline: none !important; text-decoration: none !important; -ms-interpolation-mode: bicubic !important; }
</style>
</body>
</html>
To Do
- naughty tag warnings
- performance tests
- test external CSS resource handling
- test if premailer is making image URI's absolute where possible
- complain about images with relative urls
- complain about image not having alt, height, width
- create background attribute from css where relevant
- check for lines starting with a period
Contributing
- Fork it
- Create your feature branch (
$ git checkout -b my-new-feature
) - Commit your changes (
$ git commit -am 'Added some feature'
) - Push to the branch (
$ git push origin my-new-feature
) - Create new Pull Request