My Current HTML Email Development Workflow

Massimo Cassandro
Massimo Cassandro

Each one of us has our own approach to web development: a preferred editor, some helper tools, a personal project flow, and so on. When we deal with big or complex projects, it is essential to have a clear development path, to both save time and minimize errors.

In my experience, this is especially important when working on an HTML email project. Email requires so many repetitive tasks that are not particularly complex in themselves (not very often, at least), but which can become problematic because of the sheer number of elements and tasks that need to be checked.

Here I’ll try to explain my personal workflow for HTML email development. I hope it’s useful for you to cherry-pick the parts you like.

A Typical Email Development Workflow

A classic email development workflow has three primary steps (see my Crash course article for more details):

  • Authoring (with preliminary local testing)
  • CSS inlining
  • Testing

Email Workflow

The final testing (with inlined CSS) is the step that requires more time since we’ll probably have to repeat it many times. Moreover, the “CSS inlining” and “Testing” tasks require a little extra work and attention: first, you’ll have to take care of preserving your original working copy from the inlined one. Also, the final testing requires you to send your inlined HTML to various accounts to check your design against various email clients.

Sending your code by email is a little tricky, since most clients don’t allow you to compose an email by pasting HTML code in its body (the only one I know is Thunderbird). But every test requires many actions to compose the mail, inline the CSS, paste the code, etc.

Thunderbird paste HTML window

If you have a testing platform account (Litmus, Email On Acid, Campaign Monitor or some other), you can simplify the final testing task by submitting your inlined code to the test platform but in order to perform a more accurate test, you’ll still have to send them your code by mail. In the past, I used a little PHP script to send the test emails, which could save some time, but it still requires repeating certain tasks.

Going back to CSS, you’ll probably have to deal with two files: one to be inlined and one to be embedded (for the clients that support media queries).

You’ll have to edit the CSS to be inlined directly into your HTML file, then launch an inliner tool (the Mailchimp inliner, for example), and finally you have to embed the second CSS into the inlined file (It bores me just writing about it!).

We can now review our workflow scheme in a more detailed way:

Email workflow detailed

To get a real productive workflow, many issues are still unresolved, and repetitive steps are significantly above the creative ones, which rarely leads to a good job.

Luckily, we still have some cards to play: Preprocessors and task runners.

Adding HTML and CSS Preprocessors

When I began to use preprocessors, I immediately realized how they could be useful for email development. The need for extremely verbose code (especially for HTML), can be easily simplified by a preprocessor, both for HTML and for CSS.

I mostly use Jade for HTML and Less for CSS, but you can choose the technologies you prefer. Jade is really useful when dealing with repetitive and confusing code, like nested tables. Take a look at the following sample of a three-level deep table.

<table width="100%" id="wrapper">
      <td width="100%">
        <table align="center" class="header">
              <td width="100%">
                <table width="100%">
                      <td>cell 1</td>
                      <td>cell 2</td>
                      <td>cell 3</td>

The Jade lines that produce the same code are as follows:

table(width="100%" id="wrapper")
        table(class="header" align="center")
                      td cell 1
                      td cell 2
                      td cell 3

As you can see, there are no more problems with unclosed tags and the code is easy to read.

Using Jade you can create complex templates and build your own snippets library, reusing your code in more projects. You can do the same with Less or Sass.

You can compile your files with Gulp or Grunt, but in order to have a quick preview of you work, I’ve found that the best solution is offered by Coda and CodeKit.

The “local testing” task in our workflow gives us an initial feedback on our work, and it is essential that it doesn’t require additional actions to be performed.

CodeKit compiles our Jade and Less files on saving, and your project can be previewed in real time. Coda, on the other hand, allows you to edit your files and preview the auto-refreshed compiled file in a unique window:

Coda Workspace


All these steps are completely automated, and you can focus your work on design rather than on less interesting, repetitive tasks.

Now, we have our Jade and Less file for authoring and the compiled HTML and CSS file for preview. The next step is to put it all together for the final test.

Rapid testing with Gulp

I’ve researched a lot for some Gulp or Grunt script to automate the final steps of our workflow. npm offers many solutions, but in the end I chose the Gulp Email Builder package. This package is the Gulp version of a bigger project, and it has a Grunt version too if you prefer.

Email Builder allows you to inline or embed CSS files, to perform tests using the Litmus APIs, and to send additional email for testing.

To use Email Builder, you need, of course, to install Gulp. I’ve just wrote about this in my Customizing Bootstrap Icons using Gulp article, so you can take a look at that for help. In addition, you can read Etienne Margraff’s article on Gulp and Grunt workflows.

Besides Email Builder, we will use the Gulp-Replace package, so you need to install that too.

As with every Gulp task, we have to set up gulpfile.js:

var gulp = require('gulp'),
    emailBuilder = require('gulp-email-builder'),
    replace = require('gulp-replace');

var current_date = new Date().toString(),

    email_subject = 'Food Service',

    remote_imgs_basepath = '',

    email_builder_options = {
        encodeSpecialChars: true,

        emailTest : {
            // Your Email
            email : ',' + 
                ',' +

            // Your email Subject
            subject : email_subject + ' [' + current_date + ']',

            // Optional
            transport: {
                type: 'SMTP',
                options: {
                    service: 'gmail',
                    auth: {
                        user: '',
                        pass: 'my_password'

        litmus : {
            username : 'Litmus_username',
            password : 'litmus_password',

            // Url to your Litmus account
            url : '',

            // Optional, defaults to title of email or yyyy-mm-dd if <title> and options.subject not set
            subject : email_subject,

            // Email clients to test for. Find them at
            // and use the `application_code` field
            applications : ['androidgmailapp','gmailnew', 'iphone5s']

gulp.task('default', function() {
    .pipe(replace(/src="imgs\//g, 'src="' + remote_imgs_basepath))

First, we include all the packages we need and set four variables:

  • current_date is a string that represent the current date; we will use it to differentiate testing email subject lines, making it easier to distinguish the different versions.
  • email_subject
  • remote_imgs_basepath is the url of the remote folder that contains our image. I use this to perform the local test by setting a relative path for images (in this way I can easily make all the changes I need), but the final tests (and the sending task) require that the images are uploaded in a remote folder, thus I change all src attributes with remote_imgs_basepath using Gulp Replace
  • email_builder_options is an object to configure Email builder

In this example, the email_builder_options object has three elements, you can check out the email-builder-core page for a complete list of all available options.

The first element, encodeSpecialChars, ensures that all special characters are encoded to their HTML numerical form.

The emailTest element is used to set up email testing. It requires some parameters:

  • email: the comma separated email addresses to which we send the test email. I have an account for each email service I need to test (Gmail, Outlook, Yahoo, etc.), to quickly check them both in their web mail page and in mobile apps.
  • subject: the subject of the mail (note that I have added the current_date variable to quickly recognize which version I’m dealing with).
  • transport: the parameters the sender needs in order to perform this task

If you are using GMail for the transport parameter, you need to activate the “Allow less secure apps” in your Google account settings, otherwise the sending task will fail (best if you don’t use your personal account for this):

Google Secure Apps

The third parameter allows you to set up a test on the Litmus platform (of course, you need a Litmus account). You must indicate your account parameters, an optional subject (it will be used to group tests, if you execute them more than once), and a list of email clients to be tested.

To add a client, you must use its testing application code. You can get this from the application_code field of the file (note that you have to be logged in to access this file).

In the above example, the line

applications : [‘androidgmailapp’,’gmailnew’, ‘iphone5s’]

tells Litums to test our email with Gmail App (Android), Gmail (Explorer), and iPhone 5s (iOS7).

Results can be viewed on Litmus just like hand-made ones:

Testing result on Litmus

Of course, you can remove the litmus parameter from email_builder_options if you only want to perform email tests.

The last lines of our gulpfile do all the work:

  • We first tell Gulp to use the explore_and_taste.html file for our job (this is the HTML produced by CodeKit from our Jade file, which we have just used for the first preview)
  • Using the replace module, all local paths are replaced with the remote path we’ve previously set (replace(/src="imgs\//g, 'src="' + remote_imgs_basepath))
  • Finally, the EmailBuilder task is performed, tests are sent to Litmus and to the email addresses, and the ready-to-send file is registered.

And the CSS files?

Email Builder really simplifies this task in a definitive way. You have only to add a data attribute to your link or style tags to manage them:

  • link or style tags without data attributes will be inlined
  • If they have a data-embed attribute, the CSS rules will be embedded
  • Finally, data-embed-ignore allows you to set some CSS rules for development purposes only (they will be ignored on processing).

Again, Coda can simplify Gulp processing, allowing you to use its internal Terminal app:

Gulp processing in Coda


Now we can definitively rearrange our workflow:

The final workflow

You can customize each of these steps according to your needs, using another editor and not using CodeKit, Grunt instead of Gulp, and Sass instead of Less, and so forth. Whatever technologies you choose, a workflow like this can really improve your productivity.

Let me know in the comments if you have your own HTML email workflow and how it differs from the one presented in this tutorial.