Optimize Your Email Development Workflow
•6 minEmail HTML is simply tedious. Even if you code defensively to avoid common problems, you have to write more code and use more build steps than developing for the web. Here is how I’ve streamlined my workflow with ruby to code Emails as faster.
Workflow Components
- Sinatra
A simple web server, that also compiles my css and HTML templates with data. - Roadie
The most stable CSS Inliner available. - Thor
Manages the entire workflow in a simple command line application.
Let’s start from the beginning.
I. Sinatra for Development
In order to leverage templates and CSS for reusable components, we need a web server to compile our code and let us view it in the browser. I use Sinatra, which also has some other useful features:
- compiles ERB templates (optionally with data) to HTML
- compiles Sass to CSS
- separate reusable components into smaller snippets or partials.
content_for
helper from the Sinatra::Contrib extension to inject HTML into certain places, for example: extra legal copy for footers.
Most importantly, using a web server is crucial for creating responsive components and testing them with real data.
Architecture for Reusable Components
In a design system, components can be used across many emails. To keep code manageable:
- Split code into smaller templates aka partials.
- Feed different data to these partials to see how the design adapts (or not) to real content.
To understand why this is important, consider this example from E-Commerce:
- product with a really long name
- product on sale
- product with optional properties, like description
A) YAML Data
Instead of creating partials for each edge case, we code once and feed different data to our template.
I use YAML to mock data because of its flexibility, which lets me create whatever schema I want. A sample product might look like this:
product:
title: My Awesome Poster
image: poster.jpg
availability: Ships in 24 hours
price: €25.00
meta:
- key: Color
value: White
- key: Size
value: S
- key: SKU
value: abc123
JSON is also a flexible data format. But I find the syntax tiring and polluted with "
s.
B) Partials - Code Snippets
Let’s return to our product example. We might have markup resembling:
<table>
<tr>
<td>Item</td>
<td>Qty</td>
<td>Subtotal</td>
</tr>
<% products.each do |product| %>
<%= partial :product, locals: product %>
<% end %>
</table>
where the product
partial might look like this:
<tr class="prod">
<td class="p-img"><%= image_tag url %></td>
<td><%= title %></td>
<td><%= quantity %></td>
<td class="p-eur"><%= price %></td>
</tr>
Note:
-
Every product has its own row
Using one table lets mobile Gmail stretches all content to full width of mobile devise. Otherwise with a short headline, you have: -
Why are the .prod, .p- selectors etc. repeated?
I use short names for file size optimization. So.p-
prefix indicates a product. Outlook does not support nested class name selectors. So need to repeat our.p-
prefixed selectors for our grid gutter resets. For details on why we need this, see a Gmail first Strategy for Responsive Emails.
C) Sass for Styling Shared Components
Creating and maintaining modular components is a nightmare without CSS. While many Email developers prefer to write inline code for granular control, I need CSS. I also want different CSS per Email. To do this, I leverage my development server by having templates reference two stylesheets:
<link rel="stylesheet" href="<%= @template %>/inline.css">
<link rel="stylesheet" href="<%= @template %>/include.css">
each, which look something like this:
@import "../shared/inline"; // or include
.custom-style {
…
}
In this manner I only include the CSS I actually need, which makes code easier to debug and helps optimize email file size.
But the next step, inlining CSS, is the most brittle part of any email developer’s workflow. It means giving up control to other software, which changes our code and can introduces bugs.
II. Roadie to Inline CSS
Most bugs from inlining CSS are a result of the failure to separate CSS and unwanted code optimization. Here are the most common problems:
-
Failure to separate CSS to be inlined
These styles should inlined and then discarded, so we don’t bloat our emails with unused code. -
Failure to separate CSS to be included
CSS resets, media queries, etc. Many CSS inliners screw this up and try to inline this too. -
Outlook wrapper tables mistakenly removed
Responsive layous often require extra wrapper<table>
s just for Microsoft Outlook 2007/10/10. We target these clients by using theif mso
comment, for example:<!--[if mso]><table><tr><td><![endif]-->
Some inliners will remove these comments, thus breaking your layout entirely in Outlook 2007/10/13.
Thankfully Roadie has none of these problems. I have tried many other inliners. But Roadie is the most stable - by far. See Issue #2 of Responsive Email Development Newsletter for more about CSS inliners.
III. Thor - altogether now
Finally, I bring together Sinatra and Roadie as a small command line application, from which I can:
- Add/Remove emails based on templates
- Start/Stop a development server
- Upload images to S3
- Send Test emails using SMTP
- Compile HTML and Inline CSS
File Management
I chose Thor because I also wanted a tool to help me quickly create and delete files on my hard drives based on templates and a set file naming convention, for example:
julieng@air in example on demo
$ antwort new foo
create assets/css/foo
create assets/css/foo/include.scss
create assets/css/foo/inline.scss
create assets/images/foo
create data/foo.yml
create emails/foo/index.html.erb
julieng@air in example on demo*
$ antwort remove foo
Are you sure you want to delete 'foo', including its css, images and data? (y/n) y
remove data/foo.yml
remove assets/css/foo
remove assets/images/foo
remove emails/foo
Inline Paritals
Thor lets you easily create commands with various options. I wrote my own system largely because I also wanted to inline my partials and preserve some of the logic.
For example, I can pass a --partials
flag like so:
julieng@air in example on demo
$ antwort build --partials order-confirmation
create build/order-confirmation-20151204174822/source/inline.css
create build/order-confirmation-20151204174822/source/include.css
create build/order-confirmation-20151204174822/source/order-confirmation.html
create build/order-confirmation-20151204174822/order-confirmation.html
create build/order-confirmation-20151204174822/_product.html
create build/order-confirmation-20151204174822/_table.html
create build/order-confirmation-20151204174822/_order-confirmation.html
Any file prefixed with an underscore _
also has the ERB code preserved. So the _product.html
partial might look like this:
<table border="0" width="100%" cellpadding="0" cellspacing="0" align="left" class="product">
<tr>
<td align="left" style="padding-bottom:10px;font-size:20px;line-height:18px;font-family:Arial, sans-serif;color:#003366">
<strong>{{ name }}</strong>
{% if description %}
<br>{{ description }}
{% end %}
</td>
</tr>
<tr>
<td align="left" style="font-size:12px;line-height:16px;font-family:Arial, sans-serif;color:#333333">
{% for m in meta %}
{{ m.key}}: {{ m.value }}<br>
{% end %}
</td>
</tr>
</table>
which is useful because it can be easily adapted for any other application.
Conclusion
I wouldn’t be able to create responsive email layouts with support for Gmail, if I couldn’t do it fast. I couldn’t find anything that fit my needs, so I wrote my own toolkit as a ruby gem. Currently it is only available to my clients. But if you’re interested, let me know.
If you’re looking for a workflow, try Lee Munroe’s Grunt Email Workflow. It is pretty good and will get you most of the way there. Just be aware that:
- The juice inliner is buggy (in my experience).
- Be careful about separation of inlined vs included CSS as I described above.
- It cannot inline partials and preserve logic ;-)
Have fun!