Julie Ng

Optimize Your Email Development Workflow

6 min

Email 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.

Julie's Email Development Workflow

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:

  1. Split code into smaller templates aka partials.
  2. 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:

  title: My Awesome Poster
  image: poster.jpg
  availability: Ships in 24 hours
  price: €25.00
    - 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:


  <% products.each do |product| %>
    <%= partial :product, locals: product %>
  <% end %>

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>


  • 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:

Most common problems with inlining CSS

  • 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 the if 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">
    <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 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 %}

which is useful because it can be easily adapted for any other application.


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.

More about the Antwort gem →

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!