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.
No signup · Five clients free, Outlook Classic included · All 21 with an account
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.
<!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.
͏‌  ­͏‌  ­͏‌  ­͏‌  ­
</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. Thexmlns:vandxmlns:onamespaces let Outlook draw the VML button,x-apple-disable-message-reformattingasks 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: darkblock. 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 abgcolorattribute for Outlook. - The logo. A
widthattribute,display: blockto 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:
padding: Works in 20 of 21 · partial in 1.width: Works in 20 of 21 · partial in 1.max-width: Works in 20 of 21 · partial in 1.border: Works in 20 of 21 · partial in 1.line-height: Works in 20 of 21 · partial in 1.letter-spacing: Works in 20 of 21 · partial in 1.text-transform: Works in 20 of 21 · partial in 1.
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.
- Columns. Outlook ignores
max-width, so side-by-side columns need a ghost table: see Outlook ghost tables or the ghost table generator. - Background images. background-image is not supported in Outlook Classic. Use VML instead: see background images in Outlook or the VML background generator.
- Rounded buttons and gradients. border-radius is not supported in Outlook Classic. The fixes are in Outlook CSS fallbacks, and the bulletproof button tool writes the markup for you.
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.
Build an HTML email
- Responsive Email Design: Layouts Without Media Queries
- How to Inline CSS for Email (and What Can't Be Inlined)
- Email Fonts: Why Outlook Shows Times New Roman and the Fix
- Yahoo Mail Ignores Your Font? Remove the Quotes
- Email Preheader & Preview Text: HTML Code and Examples
- Email Banner Size and Image Widths That Hold in Every Inbox
- How to Send an HTML Email from Outlook or Gmail
- HTML mailto Link Examples: Subject, Body, CC and BCC
- Emoji in Email: Subject Lines, HTML and What Breaks
- Dark Mode Email: How Gmail, Apple Mail and Outlook Recolour It
- Target Gmail and Outlook with CSS: u + .body, [data-ogsc], MSO
- Email Accessibility: 8 Fixes for Screen Readers and Contrast
- HTML vs Plain Text Email: Why You Send Both, and How
Outlook
- New Outlook vs Classic Outlook: What Changes for HTML Email
- Outlook CSS Support: Property by Property, and the Fix
- Fix border-radius, Gradients and object-fit in Outlook
- Outlook HTML Email: MSO Conditional Comments and mso- CSS
- How to Create an Email Template in Outlook (New & Classic)
- Outlook Ghost Tables: Multi-Column Emails That Don't Break
- Outlook Background Images: Copy-Paste VML That Works
Gmail and Apple Mail
React Email, MJML, Tailwind and Figma
Test and debug
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.
No signup · Five clients free, Outlook Classic included · All 21 with an account
The checker needs a computer screen.
Get new email CSS fixes by email, or follow by RSS.