Skip to main content

Our email CSS engine is open source. Give it a star on GitHub 

Emailens
Blog

HTML Email: How to Code One That Works in Every Inbox

Code an HTML email as nested tables marked role="presentation" in a 600px container, inline the styles that matter, give Outlook Classic its fallbacks in conditional comments, stay under Gmail's 16KB style and 102KB clip limits, then lint and test it before you send.

The checker needs a computer screen.

A free account opens all 21 clients and keeps your checks for 7 days. Create a free account →

Philippe Kam, founder of Emailens · Published 4 Mar 2026 · Updated 28 Oct 2026

@emailens/engine 0.18.22 · data last changed 10 Nov 2026

An HTML email that looks finished in your browser can arrive in Outlook with its columns stacked, in Gmail with its footer cut off, and in dark mode with an invisible logo. This guide is the whole build in order: a starter template you can copy, the CSS that works in every client, then one short section per step, each pointing to the guide that covers it in depth.

Why HTML email code is different

Browsers share one mostly consistent CSS spec. Email clients don't: Outlook Classic on Windows renders with Microsoft Word, Gmail rewrites your class names and keeps a <style> block only in the head and within limits, and Yahoo adds wrapper styles of its own. Each supports a different subset of CSS, so the code that works everywhere is older and plainer than what you write for the web.

What Outlook supports, property by property, is in the Outlook CSS guide. Gmail's rules are in the Gmail HTML email guide. When you need a rule only one client should see, that is client targeting CSS, not a second layout system. For what real installs actually draw, see our screenshots of 24 properties in real clients.

HTML email code: a starter template you can copy

This is a complete transactional email: head, Outlook settings, preview text, logo, heading, body copy, a button and a footer. It lints with 0 errors with the Emailens CLI (emailens lint, version 0.10.0); the two warnings it does raise are deliberate and explained below the code. Replace the example.com URLs, the copy and the colours, and keep the structure.

starter.html
106 lines
<!DOCTYPE html>
<html lang="en" 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 name="x-apple-disable-message-reformatting">
  <meta name="color-scheme" content="light dark">
  <meta name="supported-color-schemes" content="light dark">
  <title>Your order has shipped</title>
  <!--[if mso]>
  <noscript>
    <xml>
      <o:OfficeDocumentSettings>
        <o:PixelsPerInch>96</o:PixelsPerInch>
      </o:OfficeDocumentSettings>
    </xml>
  </noscript>
  <style>
    * { font-family: Arial, sans-serif !important; }
  </style>
  <![endif]-->
  <style>
    @media only screen and (max-width: 620px) {
      .container { width: 100% !important; }
      .px { padding-left: 20px !important; padding-right: 20px !important; }
    }
    @media (prefers-color-scheme: dark) {
      .bg-page { background-color: #18181b !important; }
      .bg-card { background-color: #27272a !important; }
      .text-strong { color: #f4f4f5 !important; }
      .text-body { color: #e4e4e7 !important; }
      .text-muted { color: #a1a1aa !important; }
    }
  </style>
</head>
<body class="bg-page" style="margin: 0; background-color: #f4f4f5;">
  <!-- Preheader: the preview text after the subject line -->
  <div style="display: none; max-height: 0; max-width: 0; overflow: hidden; mso-hide: all; opacity: 0; color: #f4f4f5; font-size: 1px; line-height: 1px;">
    Your order ships Tuesday. Track it or change the address until Monday.
    &#847;&zwnj;&nbsp;&#8199;&shy;&#847;&zwnj;&nbsp;&#8199;&shy;&#847;&zwnj;&nbsp;&#8199;&shy;&#847;&zwnj;&nbsp;&#8199;&shy;
  </div>

  <!-- Outer wrapper: full width, centres the email -->
  <table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" class="bg-page" bgcolor="#f4f4f5" style="background-color: #f4f4f5;">
    <tr>
      <td align="center" style="padding: 32px 10px;">

        <!-- Container: 600px, shrinks on phones -->
        <table role="presentation" class="container bg-card" width="600" cellpadding="0" cellspacing="0" border="0" bgcolor="#ffffff" style="width: 600px; max-width: 600px; background-color: #ffffff;">

          <!-- Header -->
          <tr>
            <td class="px" style="padding: 24px 40px; border-bottom: 1px solid #e4e4e7;">
              <a href="https://example.com">
                <img src="https://example.com/logo.png" width="120" height="32" alt="Example Store" style="display: block; border: 0; height: auto; font-family: Arial, sans-serif; font-size: 16px; color: #18181b;">
              </a>
            </td>
          </tr>

          <!-- Body -->
          <tr>
            <td class="px" style="padding: 32px 40px 8px;">
              <h1 class="text-strong" style="margin: 0 0 16px; font-family: Arial, Helvetica, sans-serif; font-size: 24px; line-height: 32px; font-weight: bold; color: #18181b;">
                Your order is on its way
              </h1>
              <p class="text-body" style="margin: 0 0 24px; font-family: Arial, Helvetica, sans-serif; font-size: 16px; line-height: 24px; color: #27272a;">
                Order #10482 left our warehouse this morning and should arrive on Tuesday. You can change the delivery address until Monday at 6pm.
              </p>
            </td>
          </tr>

          <!-- Button: VML for Outlook Classic, a styled link everywhere else -->
          <tr>
            <td class="px" align="left" style="padding: 0 40px 32px;">
              <div>
                <!--[if mso]>
                <v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="https://example.com/orders/10482" style="height:48px;v-text-anchor:middle;width:200px;" arcsize="13%" strokecolor="#18181b" fillcolor="#18181b">
                  <w:anchorlock/>
                  <center style="color:#ffffff;font-family:Arial, sans-serif;font-size:16px;font-weight:bold;">Track your order</center>
                </v:roundrect>
                <![endif]-->
                <a href="https://example.com/orders/10482" style="background-color: #18181b; border: 1px solid #18181b; border-radius: 6px; color: #ffffff; display: inline-block; font-family: Arial, sans-serif; font-size: 16px; font-weight: bold; line-height: 48px; text-align: center; text-decoration: none; width: 200px; -webkit-text-size-adjust: none; mso-hide: all;">Track your order</a>
              </div>
            </td>
          </tr>

          <!-- Footer -->
          <tr>
            <td class="px" style="padding: 24px 40px; border-top: 1px solid #e4e4e7;">
              <p class="text-muted" style="margin: 0 0 8px; font-family: Arial, Helvetica, sans-serif; font-size: 13px; line-height: 20px; color: #3a3a3a;">
                You are receiving this email because you placed an order at Example Store.
                <a href="https://example.com/unsubscribe" class="text-muted" style="color: #3a3a3a;">Unsubscribe</a>
              </p>
              <p class="text-muted" style="margin: 0; font-family: Arial, Helvetica, sans-serif; font-size: 13px; line-height: 20px; color: #3a3a3a;">
                Example Store, 123 Main Street, Springfield, IL 62701, USA
              </p>
            </td>
          </tr>

        </table>

      </td>
    </tr>
  </table>
</body>
</html>

What each part is for:

  • Doctype, namespaces and head meta. The HTML5 doctype, lang, a utf-8 charset and the viewport tag as on any page. The xmlns:v and xmlns:o namespaces let Outlook draw the VML button, x-apple-disable-message-reformatting asks Apple Mail not to rescale the layout, and the two color-scheme tags declare that the email supports dark mode.
  • The Outlook block. Inside <!--[if mso]>, PixelsPerInch 96 keeps Outlook's scaling steady, and the * { font-family: Arial, sans-serif !important; } rule stops Outlook falling back to Times New Roman, the fix verified in email fonts. More on conditional comments in Outlook email templates.
  • The style block. Only what cannot be inlined: a media query for phones and a prefers-color-scheme: dark block. Everything the layout depends on is inline.
  • The preheader. The hidden first line of the body sets the preview text the inbox shows after the subject; the entity string after it stops body text leaking in. Why it is built this way is in email preheader.
  • Two tables. The outer table fills the width and centres the email; the inner one is 600px and shrinks to the screen through the media query. Both carry role="presentation", so screen readers don't announce rows and columns, and both repeat the background as a bgcolor attribute for Outlook.
  • The logo. A width attribute, display: block to remove the gap under the image, and alt text styled so it reads when images are blocked. Sizes for every slot are in email image sizes.
  • The button. A VML <v:roundrect> for Outlook Classic and a styled link for everyone else, the markup the bulletproof button tool writes. Why Outlook needs it is in Outlook CSS fallbacks.
  • The footer. An unsubscribe link and a postal address. Why bulk mail needs both is in the email deliverability guide.

The two warnings the linter keeps: the button's text-decoration: none is flagged for Proton Mail, which does not support the property, so the label may show underlined there; and the preheader's font-size: 1px is flagged as very small text, which is the point of a hidden preheader. The text colours are darker than a typical grey on purpose: with mid-greys, the linter reported low contrast in its preview of Gmail Android dark mode, which repaints light backgrounds but leaves mid-tone text as it is.

HTML email CSS you can use

These lists are generated from the support data our engine ships for 21 clients, so they change when the data does. Of the 300 CSS and HTML features it tracks, 6 work in every one of them:

That list is short, so in practice you also lean on properties whose only gap is a partial note in Outlook Classic, mostly about which elements they apply to. Put them on table cells and they hold:

These popular ones break somewhere. Use them where the email still reads without them, and give Outlook Classic its fallback where one exists:

  • border-radius: Works in 16 of 21. Not supported in Outlook Classic. Partial in Yahoo Mail, Yahoo Mail Android, Yahoo Mail iOS and 1 more.
  • background-image: Works in 16 of 21. Not supported in Outlook Classic. Partial in Yahoo Mail, Yahoo Mail Android, Yahoo Mail iOS and 1 more.
  • display:flex: Works in 18 of 21. Not supported in Outlook Classic. Partial in Gmail Android and Gmail iOS.
  • box-shadow: Works in 14 of 21. Not supported in Gmail, Outlook Classic, Yahoo Mail Android and 2 more. Partial in Gmail Android and Gmail iOS.
  • margin: Works in 8 of 21. Partial in Gmail, Gmail Android, Gmail iOS and 10 more.
  • @media: Works in 5 of 21. Not supported in Outlook Classic and Thunderbird. Partial in Gmail, Gmail Android, Gmail iOS and 11 more.
  • @media prefers-color-scheme: Works in 9 of 21. Not supported in Gmail, Gmail Android, Gmail iOS and 8 more. Untested in Superhuman.
  • @font-face: Works in 6 of 21. Not supported in Gmail, Gmail Android, Gmail iOS and 12 more.

Every feature, client by client, is in the email CSS support table. To check your own HTML against it, paste it into the CSS compatibility checker.

Inline the styles that matter

Gmail's apps drop the <style> block for accounts that are not Google accounts, so anything your layout or typography depends on has to sit in a style attribute on the element. Keep the block for what can't be inlined: media queries, :hover and dark-mode rules. Set font-family on every text element rather than relying on inheritance, and give cells a bgcolor attribute as well as background-color, because Outlook reads the attribute.

How inlining works and what it can't move is in CSS inlining for email. To do it without a build step, paste your HTML into the CSS inliner.

Give Outlook Classic its fallbacks

Outlook Classic needs its own markup in a few places, served through <!--[if mso]> conditional comments that other clients ignore. The Outlook email templates guide covers the comments and the mso- properties.

Stay inside Gmail's limits

Gmail keeps a <style> block only in the head and only up to 16KB; one in the body or over the limit is removed whole. Separately, it clips any email whose HTML passes 102KB, hiding everything below the cut, unsubscribe link included. The rules are in the Gmail HTML email guide and why Gmail clips emails. The email size checker tells you how close you are.

Make it work on phones

For @media: Works in 5 of 21. Not supported in Outlook Classic and Thunderbird. Partial in Gmail, Gmail Android, Gmail iOS and 11 more. So build a layout that adapts without them: a container with max-width: 600px; width: 100%, percentage column widths, and max-width: 100%; height: auto on images. Media queries then refine it where they are supported. The approaches and their trade-offs are in the responsive email design guide.

Plan for dark mode

Some clients recolour the email themselves; others apply your own dark styles. For prefers-color-scheme: Works in 9 of 21. Not supported in Gmail, Gmail Android, Gmail iOS and 8 more. Untested in Superhuman. The starter template declares both color schemes and sets its dark colours in that media query. What each client does and how to keep logos and brand colours readable is in the dark mode email guide. The dark mode preview tool shows your email in each mode.

Accessibility and the plain-text part

Set lang on <html>, give every image alt text, and keep text contrast readable: the email accessibility guide covers the rest. Send a text/plain part alongside the HTML, too; see plain-text emails.

Coding HTML emails with a framework

If you'd rather not hand-write nested tables, React Email and MJML compile components to the same table-based HTML. They save the boilerplate but bring bugs of their own, collected in React Email troubleshooting and Tailwind v4 emails in Gmail. Starting from a design file? See Figma to inbox.

Test it before it ships

The pre-send checklist and a testing workflow you can run on every change are in how to test HTML emails. If something already looks wrong in one client, look it up by symptom in email rendering bugs. For a first pass, paste your HTML into the CSS compatibility checker.

Every HTML email guide

Each step above has a guide of its own. Here they all are, grouped by the job they help with.

Getting the email into the inbox at all is a separate job: SPF, DKIM, DMARC and sending habits are in the deliverability guides.

Questions

How do I make an HTML email?

Start from a template with the HTML5 doctype and nested tables marked role="presentation", and put your content in a 600px container. Write the styles inline on each element, add the Outlook Classic fallbacks in conditional comments, and keep a hidden preheader at the top of the body. Then lint and preview the HTML in the clients your readers use before you send it.

Can I build an HTML email without tables?

Not if your readers use Outlook Classic, which renders with Microsoft Word: display:flex is not supported in Outlook Classic. display:grid is not supported in Outlook Classic. Tables lay out the same way in every client, which is why React Email and MJML still compile to them.

What goes in the <head> of an HTML email?

The HTML5 doctype, a utf-8 charset, the viewport meta tag, x-apple-disable-message-reformatting, the two color-scheme meta tags that tell clients the email supports light and dark, a <title>, and an Outlook-only block that sets PixelsPerInch to 96 and the font Outlook falls back to. A <style> block holds the media queries. The VML namespaces on the <html> tag let Outlook draw VML shapes later in the email.

How much CSS can I use in an HTML email?

Less than on the web. Of the 300 CSS and HTML features in our engine's support data, 6 work in all 21 clients we track: background-color, color, direction, font-family, list-style-type, vertical-align. Most others work in most clients and fail in one or a few, often Outlook Classic, so use them where the email still reads without them.

Should I hand-code HTML email or use a framework?

Hand-code when you need full control over every table. A framework such as React Email or MJML writes the table markup for you, and MJML and Maizzle inline the CSS as part of their build. Either way, what the inbox receives is the same table-based HTML, so test the compiled output, not the source.

Your next question

Also useful: HTML email checker · Lint HTML emails in 21 clients · Free HTML email templates, tested in 21 clients

Paste the email that broke.

The checker needs a computer screen.

Get new email CSS fixes by email, or follow by RSS.