If you’ve ever coded HTML emails, you know it’s not like coding a web page. (Unless the last time you coded a web page was in 1996.) Many of our favorite CSS techniques just aren’t available.

Embedded images are a fallback, but many clients, including Gmail, don’t display them by default. The result is a bad first impression for new mail-ees.

At Conspire, we engage our users with a weekly email. It’s kind of like FitBit for email geeks. The first item in our mailer is a histogram of the user’s sent and received message volume for the week:

(Shameless plug: To get your own email volume histogram—along with a bunch of other cool, personalized email data—sign up at goconspire.com.)

The coolest thing about the graphic above? It’s all built without a single image or any tricky CSS. Just plain, old-school HTML tables.

Want to build your own HTML email histogram?

We’ll start with a simplified version:

So you can see what’s happening, here’s the same table with 1 pixel of red background peeking through between the cells:

We’ll go through the code <tr> by </tr>.

Disclaimer: Some details necessary for proper rendering in Outlook and other, less common email clients have been omitted. For example, some clients won’t render table cells with no text inside. For the nitty-gritty, check out the template code at the end. Campaign Monitor has some very helpful advice, too.

Row 1

The first row does two things. It—

  1. establishes the widths of the table columns, including the histogram bars (32 px wide) and the spacers between them (8 px wide) and
  2. creates the padding (8 px tall) at the top of the table.

Notice that the spacer cells all have rowspan=6, so they will span down into the remaining rows.

Row 2

Rows 2–6 are the histogram bars.

In Row 2, going from left to right, we have:

  1. A spacer cell 15 px tall to set row height
  2. A cell that fills the space above Bar A
  3. A cell that fills the space above Bar B
  4. A cell for Bar C
  5. A cell that fills the space above Bar D

As with the cells in Row 1 above, which set the widths of the table’s columns, the first cell in each of Rows 2–7 sets the height of the row.

Notice the rowspan attributes on the second through last cells. Because Bar A is 2 units tall, the spacer above it spans 3 of the 5 histogram rows, leaving 2 for the bar. Bar C fills all 5 rows, so its <td> goes here in the first row, with a rowspan of 5.

Remember that the spacer cells in Row 1 above span down through Rows 2–6. That’s why there are only 5 cells per row.

Rows 3–6

Each of Rows 3–6 starts with a spacer that establishes the height of the row. Row 3 has no other cells, since the cells from Row 2 are still spanning. Bar B starts in Row 4, Bar A in Row 5 and Bar D in Row 6.

Row 7

The last row contains the bar labels, A through D. Because it has a different background color than the histogram itself, the spacer cells end in Row 6 above, and Row 7 has its own spacer cells between the labels.

And the closing </table> tag means we’re done.

Of course, it’s only interesting if it’s dynamic.

Drawing a table like the one above for real data, and so that it looks right in every email client (whole-hearted plug for Litmus here—a small island of sanity in an ocean of HTML email madness), is a larger task and one that’s mostly left to the reader.

The first step is to reduce the code above to a generic set of functions/blocks/subtemplates in your favorite templating language. We’re a Scala shop, so we use Play templates via a handy, whimsically-named project called Twirl.

By cranking up the number of rows and decomposing stuff like spacer cells and font tags out into helper functions, we’re able to draw the histogram at the top of this post.

For those lucky enough to be building in Scala, and as an illustration of the concepts for anyone else willing to dig in (Conspire has lots of love for Ruby/Rails/ERB too), here’s the code… Enjoy!