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
muddled_body = Muddle.parse(body)
email = Mail.new do
to 'some_new_customer@gmail.com'
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: 'some_new_customer@gmail.com',
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.premailer_options = {
: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">
#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