Responsive HTML email:
tables, CSS, and the pitfalls
Your campaign looks perfect in the browser and falls apart in Outlook. Familiar story. The reason is always the same: email clients render HTML by their own rules, and those rules have little to do with web standards.
This guide is concrete code: the email skeleton, inline styles, media queries, images, and dark mode. Every example has been tested in Gmail, Outlook 2019/365, Apple Mail, and Yahoo Mail.
Contents
Why tables, not divs
The web moved away from table-based layout fifteen years ago. Email did not. Outlook on Windows uses the Microsoft Word rendering engine to display HTML. Word does not understand flexbox, grid, or most of position. Tables it handles just fine.
Gmail strips the <style> block from <head> once a message exceeds 102 KB. Yahoo Mail drops CSS animations. Mail.ru ignores max-width on div elements but applies it correctly to table cells.
The conclusion: tables are the only reliable layout primitive in email. That is not a recommendation, it is a constraint.
Basic email skeleton
Every HTML email starts from the same structure. An outer table at 100% width sets the background. An inner table at a fixed content width, typically 600px. That number is not arbitrary: most desktop clients render their preview pane at 600-700px wide.
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Email subject line</title>
<!--[if mso]>
<style>
table { border-collapse: collapse; }
.fallback-font { font-family: Arial, sans-serif; }
</style>
<![endif]-->
</head>
<body style="margin:0; padding:0; background-color:#f4f4f4;">
<table role="presentation" width="100%" cellpadding="0"
cellspacing="0" style="background-color:#f4f4f4;">
<tr>
<td align="center" style="padding: 20px 0;">
<table role="presentation" width="600" cellpadding="0"
cellspacing="0"
style="background-color:#ffffff; max-width:600px;">
<tr>
<td style="padding: 40px 30px;">
<!-- Email content -->
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>A few details worth noting. The role="presentation" attribute tells screen readers this table is for layout, not data. The conditional comment <!--[if mso]> injects styles only in Outlook on Windows. cellpadding="0" and cellspacing="0" zero out default spacing, which varies from client to client.
Inline styles: the essentials
Gmail, Yahoo Mail, and several other clients strip or ignore the <style> block in <head>. The only way to guarantee styles are applied is to put them in the style attribute of every element. Verbose, but there is no alternative.
<!-- Heading -->
<td style="padding: 0 0 20px 0;">
<h1 style="margin: 0; font-family: Arial, Helvetica, sans-serif;
font-size: 28px; line-height: 1.3;
color: #1a1a1a; font-weight: bold;">
Your email headline
</h1>
</td>
<!-- Paragraph -->
<td style="padding: 0 0 16px 0;">
<p style="margin: 0; font-family: Arial, Helvetica, sans-serif;
font-size: 16px; line-height: 1.6; color: #4a4a4a;">
Paragraph text. Every property is inline.
</p>
</td>
<!-- Button (bulletproof) -->
<td style="padding: 20px 0;">
<table role="presentation" cellpadding="0" cellspacing="0">
<tr>
<td style="background-color: #4c49fa; border-radius: 6px;
text-align: center;">
<a href="https://example.com" target="_blank"
style="display: inline-block; padding: 14px 32px;
font-family: Arial, Helvetica, sans-serif;
font-size: 16px; font-weight: bold;
color: #ffffff; text-decoration: none;">
Go to action
</a>
</td>
</tr>
</table>
</td>The button above is a bulletproof button. It does not depend on background-image (often blocked by clients) and stays clickable even when images are disabled.
Tip: write inline styles by hand only for prototypes. For production, use a CSS inliner: juice (Node.js), premailer (Python/Ruby), or the built-in tool your ESP provides. They take regular CSS from a <style> block and move it into style attributes automatically.
Responsiveness via media queries
More than 60% of emails are opened on mobile. A 600px-wide message on a 375px screen turns into a horizontal scroll bar if you do not add responsive behavior.
Media query support varies widely. Apple Mail, iOS Mail, and Samsung Mail support them fully. The Gmail app on Android ignores <style> in <head>, but does support inline media queries placed in a <style> tag inside <body>. Outlook on Windows ignores them entirely.
A working strategy: fluid layout as the base, plus media queries for clients that understand them. Outlook gets a fixed width through a conditional comment.
<style>
@media only screen and (max-width: 620px) {
.email-container { width: 100% !important; max-width: 100% !important; }
.stack-column { display: block !important; width: 100% !important; }
.stack-column-center {
display: block !important; width: 100% !important;
text-align: center !important;
}
.mobile-padding { padding-left: 20px !important; padding-right: 20px !important; }
.mobile-hide { display: none !important; }
.mobile-font-large { font-size: 22px !important; line-height: 1.3 !important; }
}
</style>
<!-- Two-column layout that collapses on mobile -->
<table role="presentation" width="100%" cellpadding="0" cellspacing="0">
<tr>
<td>
<!--[if mso]>
<table role="presentation" width="600" cellpadding="0"
cellspacing="0" align="center">
<tr><td width="290" valign="top">
<![endif]-->
<div class="stack-column"
style="display:inline-block; width:290px; vertical-align:top;">
<table role="presentation" width="100%">
<tr><td style="padding: 10px;"><!-- Left column --></td></tr>
</table>
</div>
<!--[if mso]></td><td width="290" valign="top"><![endif]-->
<div class="stack-column"
style="display:inline-block; width:290px; vertical-align:top;">
<table role="presentation" width="100%">
<tr><td style="padding: 10px;"><!-- Right column --></td></tr>
</table>
</div>
<!--[if mso]></td></tr></table><![endif]-->
</td>
</tr>
</table>The key technique: display: inline-block on the column wrappers. On desktop the two columns sit side by side. On mobile, a media query switches them to display: block; width: 100%, stacking them vertically. Outlook gets a rigid table structure with fixed widths from the conditional comment.
Images and retina displays
Three rules for images in email. First: always set width and height attributes. Without them Outlook stretches the image to the full container width. Second: always add a descriptive alt text, because Gmail, Outlook, and most corporate clients block images by default. Third: for retina screens, upload images at 2x resolution and set the display size through the attributes.
<!-- Responsive retina image -->
<img src="https://cdn.example.com/hero-1200.png"
alt="Image description" width="600" height="300"
style="display: block; width: 100%; max-width: 600px;
height: auto; border: 0; outline: none;" />
<!-- Background image with Outlook fallback -->
<!--[if mso]>
<v:rect xmlns:v="urn:schemas-microsoft-com:vml"
fill="true" stroke="false" style="width:600px; height:300px;">
<v:fill type="frame" src="https://cdn.example.com/bg.png" />
<v:textbox inset="0,0,0,0">
<![endif]-->
<div style="background-image: url('https://cdn.example.com/bg.png');
background-size: cover; background-position: center;
max-width: 600px;">
<table role="presentation" width="100%">
<tr>
<td style="padding: 40px 30px; color: #ffffff;">
<!-- Content over background -->
</td>
</tr>
</table>
</div>
<!--[if mso]></v:textbox></v:rect><![endif]-->VML (Vector Markup Language) is the only way to get a background image working in Outlook on Windows. Without it, Outlook shows white. It looks archaic but works reliably through Outlook 2021 and Microsoft 365.
Dark mode
Apple Mail, Outlook.com, and Gmail (partially) invert email colors when the user has dark mode enabled at the OS level. White backgrounds go dark gray, black text goes light. The catch: inversion is automatic and not always accurate. A dark-blue logo on a white background can become an invisible dark-blue logo on a dark-gray background.
/* Dark mode styles */
@media (prefers-color-scheme: dark) {
.email-body { background-color: #1a1a2e !important; }
.email-container { background-color: #16213e !important; }
.text-primary { color: #e0e0e0 !important; }
.text-secondary { color: #b0b0b0 !important; }
}
/* Meta tag for Apple Mail */
:root {
color-scheme: light dark;
supported-color-schemes: light dark;
}The color-scheme meta tag tells Apple Mail: "this email is dark-mode ready, do not auto-invert." Without it, Apple Mail applies its own inversion, which can distort the design.
A practical fix for dark logos: add a 2-3px transparent stroke in white or a light color. The logo stays readable on any background. Or prepare two versions of the logo and switch between them with a media query.
Client compatibility
Different clients support different CSS properties. Here is a reference table worth keeping nearby.
| Property | Gmail | Outlook | Apple Mail | Yahoo |
|---|---|---|---|---|
| media queries | Partial | No | Yes | Yes |
| flexbox / grid | No | No | Yes | No |
| background-image | Yes | VML | Yes | Yes |
| border-radius | Yes | No | Yes | Yes |
| margin | Partial | Partial | Yes | Yes |
| <style> in <head> | Up to 102 KB | Yes | Yes | Yes |
| SVG | No | No | Yes | No |
Apple Mail is the only client with full modern CSS support. Every other client requires trade-offs. Progressive enhancement works here: build a solid base on tables with inline styles, then layer on improvements via <style> for clients that can handle them.
Outlook and the Word engine. Outlook 2007, 2010, 2013, 2016, 2019, and Microsoft 365 on Windows use Word to render HTML. Outlook on macOS uses WebKit and behaves like Safari. Outlook.com (web) is a separate product with its own quirks. Do not confuse them: three different rendering engines share the same brand.
Testing before you send
Coding the email is half the job. The other half is confirming it renders correctly across dozens of clients. Manual testing in each one is not realistic, so people use tools.
Litmus and Email on Acid capture screenshots of your email in 90+ clients. Starting from $80/month, they pay off for teams that send regularly. The free option is PutsMail (by Litmus): it delivers a test email to your inbox. No screenshots, but enough to check Gmail and Outlook manually.
Minimum set for manual testing:
- Gmail (web) - the most widely used client globally
- Outlook 2019/365 (Windows) - the most problematic, because of the Word engine
- Apple Mail (iOS) - 30-40% of mobile opens
- Yahoo Mail - significant share in the US and Europe
- Samsung Mail - default on Android devices
Do not forget list validation. A perfectly coded email is useless if it goes to addresses that do not exist. Validating your recipient list before sending removes hard bounces and protects your domain reputation.
Pre-send checklist
Here is what to check before every send. Bookmark it or print it out.
Layout is built on tables, not divs, flexbox, or grid
All styles are duplicated in inline style attributes
Every img has explicit width and height, plus alt text
Images are uploaded at 2x for retina, PNG or JPEG format
Buttons are coded as bulletproof (table + link)
Dark mode fallback is in place (color-scheme meta tag)
Conditional comments added for Outlook (<!--[if mso]>)
HTML size is under 102 KB (Gmail limit)
Tested in Gmail, Outlook, Apple Mail, and Yahoo
Plain-text version is included
Recipient list has been validated
Email coded. But is the list clean?
Upload your address list to uChecker and we will show you which addresses are invalid, which point to spam traps, and which have gone dark. Results in minutes.
Validate your list in uChecker