Guide

How to code an HTML email template your marketers can edit

An HTML email is a web page from 2003: tables for layout, styles written on every element, a fixed width and a list of workarounds for Outlook. This guide codes one from the doctype down, tests it, and then adds the step most guides stop short of: marking the parts a marketer may change with one attribute, data-mm, so the template becomes content fields in Modular Mail and the finished email exports to Salesforce Marketing Cloud, Braze, Marketo or any ESP without anyone touching the code again.

Updated 3 November 2026. Every snippet is plain HTML you can paste into any ESP.

Before you start

Why you can't just write a web page

Every email client renders HTML its own way, and a few of them render it badly. Outlook on Windows uses Microsoft Word's engine, which ignores most CSS layout, so columns have to be table cells. Gmail removes anything it doesn't trust, including some<style> rules. Apple Mail renders almost everything, which is a trap, because a layout that looks right there can still fall apart elsewhere.

Litmus's figures for September 2026 put Apple at 76.56% of opens, Gmail at 16.10% and Outlook desktop at 3.48%. Apple's number is inflated by Mail Privacy Protection, which opens emails on the reader's behalf, and Outlook's small share is where the business inboxes are, so a B2B sender should treat it as a first-class target whatever the chart says.

The practical rules that follow from this haven't changed much in a decade: lay out with tables, put the styles on the elements, keep the email 600 pixels wide, and test in the clients your list actually uses.

Step 01

The document and the 600px wrapper

The doctype, the Office namespaces and the conditional comment are for Outlook. The role="presentation" attribute tells screen readers the table is layout, not data.

template.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title></title>
  <!--[if mso]>
  <noscript><xml><o:OfficeDocumentSettings><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml></noscript>
  <![endif]-->
</head>
<body style="margin:0;padding:0;background-color:#f4f4f4;">
  <table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:#f4f4f4;">
    <tr>
      <td align="center" style="padding:24px 0;">
        <!--[if mso]><table role="presentation" width="600" cellpadding="0" cellspacing="0"><tr><td><![endif]-->
        <table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="max-width:600px;background-color:#ffffff;">
          <!-- the email's sections go here, one <tr> each -->
        </table>
        <!--[if mso]></td></tr></table><![endif]-->
      </td>
    </tr>
  </table>
</body>
</html>

The outer table paints the background across the whole window. The inner table is the email, fluid up to 600px so it fills a phone and stops growing on a desktop. Outlook ignores max-width, which is what the conditional 600-wide table is for.

Step 02

Inline CSS, with a <style> block for the rest

Most clients read styles written on the element. Fewer read a <style> block, so it carries only the things that can't be inlined: media queries and dark mode rules.

template.html · in <head>
<style>
  @media screen and (max-width: 600px) {
    .px { padding-left: 16px !important; padding-right: 16px !important; }
    .stack { display: block !important; width: 100% !important; max-width: 100% !important; }
  }
</style>

Write the base styles inline as you go, as the next steps do. If you'd rather write a stylesheet and inline it at the end, Juice, Premailer and MJML's compiler all do that. The classes above are the only ones the template needs: one for phone padding, one for stacking columns.

Step 03

A hero section

Each section is one <tr> in the 600px table, with its own padding, its own font declaration and nothing that depends on the row above or below. That independence is what makes a section reusable later.

modules/hero.html
<tr>
  <td class="px" style="padding:32px 40px;font-family:Arial,Helvetica,sans-serif;">
    <img src="https://cdn.example.com/autumn.jpg" width="520" alt="Walkers on a ridge in autumn light"
      style="display:block;width:100%;max-width:520px;height:auto;border:0;">
    <h1 style="margin:24px 0 8px;font-size:28px;line-height:34px;color:#1f3a2e;">
      The autumn edit is here
    </h1>
    <p style="margin:0 0 24px;font-size:16px;line-height:24px;color:#333333;">
      New layers, tested on wet Welsh hills.
    </p>
    <table role="presentation" cellpadding="0" cellspacing="0" border="0">
      <tr>
        <td bgcolor="#1f3a2e" style="padding:14px 28px;border-radius:4px;">
          <a href="https://example.com/autumn"
            style="display:inline-block;font-family:Arial,Helvetica,sans-serif;font-size:16px;color:#ffffff;text-decoration:none;">
            Shop the edit
          </a>
        </td>
      </tr>
    </table>
  </td>
</tr>

Three details carry most of the Outlook fixes. The image has a widthattribute as well as a style, because Outlook reads the attribute. It'sdisplay:block, so Gmail doesn't leave a gap under it. The button is a padded cell with a bgcolor attribute, not a styled <a>, because Outlook won't pad a link. Write font-family on the cell, not the body, since some clients drop body styles.

Step 04

Two columns, one on mobile

Outlook needs real table cells for columns. Everything else can use inline-block divs that drop to full width under 600px. The conditional comments give each client the version it understands.

modules/two-up.html
<tr>
  <td class="px" style="padding:0 40px 32px;font-family:Arial,Helvetica,sans-serif;">
    <!--[if mso]><table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td width="50%" valign="top"><![endif]-->
    <div class="stack" style="display:inline-block;width:100%;max-width:252px;vertical-align:top;">
      <img src="https://cdn.example.com/jacket.jpg" width="252" alt="" style="display:block;width:100%;height:auto;border:0;">
      <p style="margin:12px 0 0;font-size:16px;line-height:24px;color:#333333;">Ridge shell jacket</p>
    </div>
    <!--[if mso]></td><td width="50%" valign="top"><![endif]-->
    <div class="stack" style="display:inline-block;width:100%;max-width:252px;vertical-align:top;">
      <img src="https://cdn.example.com/boots.jpg" width="252" alt="" style="display:block;width:100%;height:auto;border:0;">
      <p style="margin:12px 0 0;font-size:16px;line-height:24px;color:#333333;">Fell boots</p>
    </div>
    <!--[if mso]></td></tr></table><![endif]-->
  </td>
</tr>

Leave a few pixels between the two max-width values and half the content width, so the divs don't wrap early when a client rounds differently. Product images that are purely decorative get an empty alt; images that carry meaning get a description.

Step 05

Footer, unsubscribe and merge tags

Every ESP has its own tag for the unsubscribe link and the reader's name, and most refuse to send without the unsubscribe one. Write the tag your ESP expects and leave it alone.

modules/footer.html
<tr>
  <td class="px" style="padding:24px 40px 32px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:18px;color:#666666;">
    Northwind Outdoor, 14 Quarry Lane, Keswick CA12 5XX<br>
    <a href="*|UNSUB|*" style="color:#666666;">Unsubscribe</a> ·
    <a href="*|UPDATE_PROFILE|*" style="color:#666666;">Update preferences</a>
  </td>
</tr>

The tags shown are Mailchimp's. Salesforce Marketing Cloud uses%%unsub_center_url%%, Braze{{${set_user_to_unsubscribed_url}}}, Klaviyo{% unsubscribe_url %}, HubSpot {{ unsubscribe_link }}and Marketo {{system.unsubscribeLink}}. Check your platform's docs before the first send, because a wrong tag ships as the tag itself.

Step 06

Stop dark mode inverting your logo

Apple Mail, Outlook and some Gmail apps recolour emails for readers in dark mode. Two meta tags and a media query let you decide what changes.

template.html · in <head>
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark">
<style>
  @media (prefers-color-scheme: dark) {
    .bg { background-color: #1a1a1a !important; }
    .text { color: #eeeeee !important; }
  }
</style>

Use a logo with transparency and a thin light outline, or a PNG on a solid brand-coloured cell, so an inverted background doesn't swallow it. Clients that ignore the media query will invert on their own terms; that's what testing is for.

Step 07

Test in the clients your list uses

Send the file to yourself in Gmail, Outlook on Windows and Apple Mail on a phone before anything else. Then use a rendering tool for the rest.

Litmus and Email on Acid render the HTML in dozens of clients at once and are worth paying for if you send often. For a one-off, real inboxes and a borrowed Windows laptop find most problems. Check the email with images off, because that's how Outlook first shows it, and check the plain-text version your ESP generates, which is what some corporate filters read.

Two things to check that tools don't flag: the total size of the HTML file (Gmail clips messages over about 102KB, so a long email with lots of inline styles can get cut), and the alt text on every image that carries meaning.

Step 08

Make the template editable with one attribute

The template now works. The last step decides whether it stays working: give the marketing team a way to change the content that can't change the layout. In Modular Mail that's one attribute, data-mm, on each element a campaign may change.

modules/hero.html
<tr>
  <td class="px" style="padding:32px 40px;font-family:Arial,Helvetica,sans-serif;">
    <img data-mm="image" src="https://cdn.example.com/autumn.jpg" width="520" alt="Walkers on a ridge in autumn light"
      style="display:block;width:100%;max-width:520px;height:auto;border:0;">
    <h1 data-mm="headline" style="margin:24px 0 8px;font-size:28px;line-height:34px;color:#1f3a2e;">
      The autumn edit is here
    </h1>
    <p data-mm="body" style="margin:0 0 24px;font-size:16px;line-height:24px;color:#333333;">
      New layers, tested on wet Welsh hills.
    </p>
    <table role="presentation" cellpadding="0" cellspacing="0" border="0">
      <tr>
        <td data-mm="button-cell" bgcolor="#1f3a2e" style="padding:14px 28px;border-radius:4px;">
          <a data-mm="button" href="https://example.com/autumn"
            style="display:inline-block;font-family:Arial,Helvetica,sans-serif;font-size:16px;color:#ffffff;text-decoration:none;">
            Shop the edit
          </a>
        </td>
      </tr>
    </table>
  </td>
</tr>

Each marker becomes a field in the form builder: image an image field where the marketer picks or uploads an image from the workspace's media library, which hosts it at a public URL and can fill in the alt text the image was given there, headline and body text fields with a character limit if you want one, button a link field for the text and the URL, andbutton-cell a choice field offering the approved button colours, each setting bgcolor as well as background-color so Outlook follows. Names start with a letter, use letters, digits, hyphens and underscores, and are unique within a section. Anything without a marker is locked: the marketer can't reach the padding, the font or the table.

Then split the file into the three parts the app expects. The header is everything from the doctype to the opening of the 600px table, with the empty<title></title> left in, because the campaign's subject goes there, and no preheader div, because Modular Mail inserts the preheader just inside<body> itself. Each section is a module, one<tr> each. The footer is the footer row plus every closing tag. Keep tracking pixels, conditional comments and ESP logic in the header, the footer or the unmarked parts of a module, where no field can overwrite them.

On export, the data-mm attributes are removed and the rest of the HTML is copied byte for byte, so what reaches Salesforce Marketing Cloud, Braze or Marketo is the file you tested, with the marketer's content in it. Modular Mail doesn't send campaigns, keep version history or run a rendering test of its own: test sends go to real inboxes, and the ESP does the sending.

In context

Hand-coding compared with the other ways to get an HTML email

Ways to create an HTML email template, compared
Hand-coded HTMLMJML or MaizzleBuilder export (Stripo, RGE Studio)Canva Email
Who writes itA developerA developer, in a shorter syntax that compiles to tablesAnyone, from blocksAnyone, from a canvas
Control over the Outlook fixesCompleteMostly handled by the frameworkThe builder's ownCanva's own
Editable by marketers afterwardsOnly in the code, unless the regions are markedOnly in the code, unless the regions are markedIn the builderIn Canva, then re-exported
Works as a Modular Mail templateYes, with data-mm on the editable partsYes, on the compiled outputPossible, after tidying the exportNot tested
Best forCustom designs that have to hold up everywhereDevelopers who code email oftenTeams designing from scratch with no developerOne-off emails from a Canva design

Compared on 3 November 2026. Beefree became RGE Studio in May 2026.

FAQ

Questions about coding HTML email

Something else? Ask us on a demo

How do I create an HTML email template?

Start from an XHTML transitional doctype, lay the email out with nested tables no wider than 600px, write the styles on the elements, add the Outlook conditional comments where the layout needs them, and test in Gmail, Outlook and Apple Mail. The eight steps on this page do it in order, with the code.

Why do HTML emails still use tables?

Because Outlook on Windows renders email with Microsoft Word's engine, which doesn't understand CSS layout. Tables are the one layout method every client agrees on. Inside a cell you can use modern CSS freely; it's the structure that has to be tables.

Can I use CSS Grid or Flexbox in email?

Not for the structure. Apple Mail and most Gmail clients support them, Outlook doesn't, and the fallback when they fail is a stack of unstyled blocks. Use tables for columns and inline-block for the mobile stacking, as in step 04.

Do I have to inline the CSS?

For the base styles, yes, because some clients drop or partly read the <style> block. Media queries and dark mode rules can't be inlined and stay in <style>. Write a stylesheet and inline it with a tool if you prefer; the result is the same.

Should I use MJML instead of writing the HTML by hand?

If you code email often, MJML or Maizzle saves time and handles most Outlook fixes for you. The compiled output is ordinary table-based HTML, so it works in any ESP and can be marked with data-mm like hand-written code.

How do I make the template editable without a developer for every send?

Mark the editable elements and put the template in a tool that turns the markers into fields. Modular Mail uses one attribute, data-mm, and locks everything else. The other options are your ESP's own editable regions, importing the HTML into a builder, or a developer on each change; one page compares them. How to let marketers edit HTML email templates

How do I create a custom email template in Mailchimp, Salesforce Marketing Cloud or Braze?

Each accepts pasted HTML: Mailchimp through "Code your own" in its legacy builder, Salesforce Marketing Cloud through Paste HTML in Content Builder, Braze through its HTML editor. Once pasted, the design is edited in code unless the ESP has its own editable regions. Modular Mail sits before that step: the team builds the campaign in fields and pastes the finished HTML in.

How wide should an HTML email be?

600px is still the safe maximum. It fits the preview pane in Outlook and Gmail on a desktop, and on a phone the fluid table shrinks to the screen.

How do I test an HTML email?

Send it to real inboxes in Gmail, Outlook on Windows and Apple Mail on a phone first, with images off as well as on. Litmus or Email on Acid cover the long tail of clients. Modular Mail sends tests to up to 10 addresses from the editor; for rendering across clients, run the exported HTML through your testing tool.

Can I download a finished template to start from?

Yes. Every template in the free library opens in the editor with no account. Change a few fields and export, and the download is the full HTML, tables and Outlook fixes included. Open a free template

Send us the template you've coded.

We'll mark it up with data-mm before the call and show it to you as fields, so you can see what your marketing team would be editing, and what they couldn't touch.