MacGyvering HTML email using Perl's Template Toolkit

Producing HTML email is generally a frustrating endeavor akin to traversing a minefield one step at a time. I help some non-profits generate email newsletters from Word documents submitted by various contributors. The way I have done this for a long time was to first reduce each piece to the plainest HTML possible, attach classes to HTML elements, and define the CSS separately. Then, a quick script takes in the HTML documents, and the CSS, and inserts the CSS for each element in to its style attribute. This helped me streamline the generation of some rather involved layouts.

In this case, I ship the HTML, and someone actually pastes the content into a textbox on a system they have been using for all eternity.

Today, I started thinking about how to do this sort of thing without writing any code, without manipulating any trees etc etc.

I know ... young hipsters these days probably have some gigabytes of Node infrastructure in their home directories with watchers, digesters, gulpers, notifiers and the like to do this kind of thing, but I have Perl and the Template Toolkit!

So, I thought how far I could get by just using Template Toolkit as a macro language.

Template Toolkit comes with two handy utilities tpage and ttree. For the purpose of generating a single HTML page to be used in an email, tpage should be enough: It takes a file adorned with TT tags, processes it, and produces output on STDOUT.

Note that all the code mentioned in this post is available on GitHub.

tpage

Among other things, tpage allows you to define template variables, specify include file locations, choose standard files to process before and after your template source, and change the tags used by TT. Changing the opening and closing tags from the default set of [% and %] to <% and %>, respectively, will help make variable interpolations in the main document look more like plain old HTML tags.

Assuming each organization has a consistent set of styles, I am going to put all the definitions I need in a separate file, and shove it in an include directory. I am also going to utilize tpage's --trim, --pre-chomp, and --post-chomp options to make sure there are no undesirable spaces introduced into the final HTML.

Typing all that on the command line can get tedious, so, first, let's create a configuration file for tpage:

pre_chomp
post_chomp
trim
start_tag=<%
end_tag=%>
include_path=include

Here is a simple example of how you would use this configuration file:

C:\...\tt-html-email> tpage -f .tpagerc
<% p.blurb = '<p style="font-size:16px">' %>
<% p.blurb %>This is a blurb.</p>
^Z
<p style="font-size:16px">This is a blurb.</p>

Of course, on *nix systems, use CTRL-D instead.

So, the main idea is to put definitions such as <% p.blurb = '...' %> in an include file, and put together the HTML document using these tags.

The p.blurb above refers to the key blurb in the hash p. That is, what we have here is just variable interpolation: Not a function or method call, not a plugin invocation. So, for example, you can't really do <% a title="..." href="..."<% img src="..." %>></a> or something similar.

Instead, you can put each such visual element in its own variable. This does have the benefit of reducing typing. Here is a starter document:

<% body.open %>

<% header %>

<% content.open %>

<% content.close %>

<% sidebar.open %>

<% sidebar.close %>

<% footer %>

<% body.close %>

For this newsletter, I am going to use a two-column layout consisting of a main content area and a sidebar area. Above it all, however, is the heading of the newsletter.

I am going to use the --wrapper option to define a skeleton HTML document in which to insert the processed template output. Most tools that send out your email do strip out a lot what goes in the <head>, but I have found it useful to put a doctype and charset in there to help whatever is parsing the content. Here are the contents of the wrapper:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><% template.title %></title>
</head>
<body>
<% content %>
</body>
</html>

You set the template.title field in your source file using a <% META %> directive:

<% META title = "ν42 March Newsletter Sample" %>

The META directive is very useful in general. Here, I am only using it to use separate namespaces for my HTML building blocks and sundry content fields. With this in place, running tpage gives me:

C:\...\tt-html-email> tpage -f .tpagerc source.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>ν42 March Newsletter Sample</title>
</head>
<body></body>
</html>

Now, the hard part begins. As an example, I am going to follow MailChimp's guide. With that in mind, body.open becomes:

<% body.open = '<table border="0" cellpadding="0" cellspacing="0" height="100%" width="100%" id="email-body-container"><tr><td align="center" valign="top"><table border="0" cellpadding="20" cellspacing="0" width="600" id="email-container">' %>

and, body.close becomes:

<% body.close = '</table></td></tr></table>' %>

We put these definitions in a file, say, include/tags.html, and tell tpage to process it before the source file using the --pre-process option in .tpagerc:

pre_process=tags.html

You can see where this is going. So, let's add a few more definitions to tags.html:

<% body.open = '<table border="0" cellpadding="0" cellspacing="0" height="100%" width="100%" id="email-body-container"><tr><td align="center" valign="top"><table border="0" cellpadding="20" cellspacing="0" width="600" id="email-container">' %>

<% body.close = '</table></td></tr></table>' %>

<% header = '<tr><td colspan="2"><a href="https://www.nu42.com/"><img src="https://example.org/some/image.png" width="600" height="100" alt="[ &nu;42 Newsletter Header Logo ]" title="&nu;42"></a></td></tr>' %>

<% content.open = '<tr valign="top" align="left"><td width="80%">' %>

<% content.close = '</td>' %>

<% sidebar.open = '<td width="20%">' %>

<% sidebar.close = '</td></tr>' %>

<% footer.open = '<tr valign="middle" align="center"><td colspan="2"><p>Some footer text</p><p><a href="https://www.nu42.com/">&nu;42 online</a></p></td></tr>' %>

<% h2.story = '<h2 style="font-weight:bold;font-family:serif;font-size:16px">' %>

<% p.story = '<p style="font-family:sans-serif;font-size:14px">' %>

<% style.a.story = 'color:#22e;text-decoration:none' %>

<% h3.sidebar = '<h3 style="font-weight:bold;font-family:serif;font-size:14px;text-decoration:underline">' %>

<% p.sidebar = '<p style="font-family:sans-serif;font-size:12px"' %>

<% style.a.sidebar = 'color:#446;text-decoration:none' %>

and add some content to source.html:

<% META title = "ν42 March Newsletter Sample" %>

<% body.open %>

<% header %>

<% content.open %>

<% h2.story %>A tar anomaly</h2>

<% p.story %>A bug-fix to File::Which caused me to discover some unexpected (by-design) behavior of MinGW and Cygwin versions of the tar utility on Windows: Files may evaporate on extraction &hellip; <a href="https://www.nu42.com/2016/03/tar-anomaly.html" style="<% style.a.story %>">more</a></p>

<hr>

<% h2.story %>Perl is spreading to Windows</h2>

<% p.story %>Installing git alongside Visual Studio 2015 Update 2 CTP brings perl 5.22.0 to Windows developers´ machines &hellip; <a href="https://www.nu42.com/2016/02/perl-spreading-windows-visual-studio.html" style="<% style.a.story %>">more</a></p>

<% content.close %>

<% sidebar.open %>

<% h3.sidebar %>Related Sites</h3>

<% p.sidebar %><a href="http://www.masteringperl.org/" style="<% style.a.sidebar %>">Mastering Perl</a></p>

<% p.sidebar %><a href="http://www.effectiveperlprogramming.com/" style="<% style.a.sidebar %>">Effective Perl</a></p>

<% p.sidebar %><a href="http://perltricks.com/" style="<% style.a.sidebar %>">Perl Tricks</a></p>

<% p.sidebar %><a href="http://perlmaven.com/products" style="<% style.a.sidebar %>"> Perl Maven</a></p>

<% p.sidebar %><a href="http://perlweekly.com/" style="<% style.a.sidebar %>">Perl Weekly</a></p>

<% sidebar.close %>

<% footer %>

<% body.close %>

Of course, if the links that go in the sidebar never change, you might just want to put all of that in another file, and include it from your source to keep clutter to a minimum.

You can then use something along the lines of

tpage -f .tpagerc source.html > newsletter-2016-03-15.html

to save the output to a file, and then paste or upload it to whatever system you need to deal with.

I haven't yet completely settled on this technique, but it does seem less convoluted than writing in a restricted dialect of HTML, and then weaving in CSS into style attributes of various elements.

PS: You can discuss this post on r/perl.