My Current HTML Email Development Workflow

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

  • Allow me to show you a (fantastic ways to earn a lot of extra money by finishing basic tasks from your house for few short hours a day — See more info by visiting >MY___{DISQUS}___ID::

  • Lawrence

    Very interesting, even though I understood 45% of it. And this is important because coding emails is a complex affair that often those on the business side of things don’t appreciate. Grazie!

  • … and all this ‘thanks to’ Outlook @Microsoft :)

    • Outlook has a lot of issues, but it is not the only. Also Gmail, for example, needs a lot a work…

  • Jason

    @danielgheorghe:disqus Depending on client, we’re seeing Outlook account for less then 9% of opens these days. I’ll gladly eat it on potential conversions for those recipients versus sacrificing any design or code that would potentially convert even 1% more mobile or Gmail readers.

    I’ve also begun getting a little cheeky with a ‘If you’re reading this email in Outlook…’ type message.

  • Jason

    @Massimo Cassandro

    Not to discredit this article or your clearly organized and well defined workflow (kudos), but as a designer (with a decent knowledge of CSS techniques and HTML), I feel this is rather over the top given the scope of most campaigns, client requirements and more importantly the relationship between design and execution. Litmus, Email on Acid and the testing available at Mailchimp and CM are absolutely no substitute for gold old fashion real world testing to every platform and device.

    I have the utmost respect for skilled developers, and while I don’t doubt that at scale these techniques may be valuable, but in practice they would appear to create an even greater disconnect between designer and developer (very rarely can the latter be as effective as the former at email design for their respective titles). Your workflow tends to use the word ‘design’ synonymously with code – something we designers struggle with constantly during the delicate collaborative process in producing amazing emails. While I believe this piece intended to speak more to the process AFTER design has been received, it disregards and makes no mention of the importance of the involvement of designer(s) at any stage.

    • Direct testing on every platform and device can be (in same cases) a good way, but in the same time can be very tricky and, of course you need to have access to all them.

      My article doesn’t deal about design aspects, it deals about the workflows (and it starts when the email design is already defined and approved) but this doesn’t mean that you couldn’t add design steps on it.

      About designers and coders. This is a very old question and many people wrote about this topic. In my opinion, separation between design and coding is not a good practice: designer and coder need to share and mix their skills constantly, so I think that best solution is that a designer is a coder too (at leat for emal development).

      Only in this way it is possible to have a costant attention to coding, UX issues, etc and find the best and innovative solutions

  • @thecunningfox:disqus
    I don’t feel offended, do not worry. I really think that these exchanges of ideas are very precious.

  • Matt Dawson

    Thanks Massimo for a great guide! I have started using .jade and .less in my workflow and i’m enjoying the results. Unfortunately i am getting a ‘Unhandled rejection Error: connect ETIMEDOUT’ error during the email builder send. I am using a gmail account and have the ‘Allow less secure apps’ turned on. Do you know what the issue could be?


    • Hi Matt, I’m sorry but I have never experienced that error. Maybe something concerning your development environment?

  • The trick to HTML emails is to keep them terribly simple. They should only follow a few simple rules to keep with the company’s CI and more concentration should be put on the textual content in them. Doing that will go a long way to making the HTML email creation process as quick and painless as possible. The other trick is to have a templating system that allows the design rules mentioned above to be created once and used at will by the marketing department.

  • Douglas Irvine

    The other great thing about Thunderbird is you can view the source of the email you’ve sent. In many cases, you won’t have complete control over the markup going out. This lets you see exactly what is being generated.

  • Joseph

    Hi Jason,

    I work for a company that has a number of brands, each of which sending a number of emails every day to a single development team. This statement isn’t practical for us:
    “Most any designer/developer (or team) worth their sand will have any device or email platform at their disposal to test in real word (versus emulators).”

    We use Litmus to test email campaigns because we test on over 20 email clients… we do this because these channels generate revenue. Having twenty different physical devices, each one configured to test a single email, would: 1) take a significant amount of time, and significant space… where/how do you house 20 different operating devices (best buy)? 2) create unnecessary overhead for our team to maintain.

    I think this post is about the workflow process not design, the meat of which could help a team like ours reduce development time which saves us time and money. To be honest, I think your statement on developers as designers encapsulates your misunderstanding in general, “Over the years I’ve yet to find a cracker jack and highly skilled developer who has any merit at design. I’m sure they exist, but may just as well be unicorns.”

  • johdah

    Hi. This was really helpful! However, I would like to separate the ‘inlining’ from the ‘sending & testing’. Since when developing my emails, I don’t want to run a test and an email to myself each time I inline some CSS. How can I break these apart? I would like to have separate gulp tasks for ‘inlining, ‘sending mail’ and ‘litmus testing’. How can this be done?

  • I think that in order to perform only css inlining, you can simply comment the emailTest object in your emailBuilder option

  • Juanfevasquez

    Thanks Massimo and Jason, your discussion has added a lot of extra information that is really useful.
    @massimocassandro:disqus have you tried Inky and using the Zurb Framework for Email development? So far my results have not been very positive when testing in Litmus :(

  • Jason

    Hey Dom,

    The core of that code is from the Mason Theme by Rocketway. I just checked out Jason’s book and site – very cool stuff. While I don’t know the timing and release of that template in relation to his book/course, I don’t doubt the possibility there was some ‘sharing’.

  • Nilesh Parmar

    Don’t worry i sorted my problem, lol.
    service should be iCloud, not mail. And I do not include secure connection: true,

Get the latest in Front-end, once a week, for free.