Outlook Rendering Fixes: Engineering Reliable Email Layouts for Microsoft Clients
Diagnose and fix Outlook rendering bugs—VML fallbacks, MSO conditional comments, and Word engine workarounds for production HTML emails.
Microsoft Outlook's reliance on the Microsoft Word rendering engine introduces unique constraints for modern email development. Unlike WebKit or Blink-based clients, Outlook strips modern CSS, ignores float, flexbox, and CSS Grid, and enforces strict table-based layouts. Addressing these constraints requires a systematic approach to conditional comments, VML fallbacks, and inline style normalization. For foundational strategies on structuring resilient markup, refer to Mastering Email HTML & CSS.
Conditional Comments & MSO Targeting
Outlook 2007 through 2019 (Windows) utilize the Word HTML parser, which completely ignores standard @media queries and modern CSS selectors. Developers must wrap client-specific overrides in <!--[if mso]> conditional statements. This technique allows you to inject table-based fallbacks without affecting Apple Mail or Gmail. When combined with Responsive Email Layouts, conditional targeting ensures fluid grids degrade gracefully into fixed-width structures on desktop clients.
Production Implementation Pattern
<!--[if mso]>
<table role="presentation" border="0" cellspacing="0" cellpadding="0" width="600" align="center" style="width:600px;">
<tr>
<td>
<![endif]-->
<div class="fluid-container" style="max-width: 600px; margin: 0 auto;">
<!-- Modern fluid content here -->
</div>
<!--[if mso]>
</td>
</tr>
</table>
<![endif]-->
Debugging Steps
- Syntax Validation: Missing
<![endif]-->breaks the DOM tree in Outlook. Always validate conditional blocks with an HTML linter before deployment. - Version Targeting: Use
<!--[if gte mso 9]>to target Outlook 2007+ specifically. Avoid wrapping Outlook for Mac (which uses WebKit) in MSO conditionals, as it will render the fallback instead of the modern layout. - Source Inspection: Export rendered emails to
.mhtmlor use Outlook's "View Source" to confirm conditionals survive ESP transit. Many transactional senders strip HTML comments by default; disable comment stripping in your ESP configuration.
VML for Backgrounds & Hero Sections
Background images and gradient overlays fail in Outlook due to the lack of background-size, background-position, and background-image support on block elements. The Vector Markup Language (VML) specification provides a reliable workaround, and the full pattern for hero and full-bleed imagery is documented in email background images and VML. By generating <v:rect> and <v:fill> elements dynamically via build tools or MJML, engineers can maintain visual parity across platforms. This approach is particularly critical when implementing Dark Mode Email CSS, as VML requires explicit color overrides to prevent rendering artifacts in high-contrast environments.
Production VML Fallback
<div style="background-image: url('https://cdn.yourdomain.com/hero.jpg'); background-size: cover; background-position: center;">
<!--[if mso]>
<v:rect xmlns:v="urn:schemas-microsoft-com:vml" fill="true" stroke="false" style="width:600px;height:300px;">
<v:fill type="tile" src="https://cdn.yourdomain.com/hero.jpg" color="#f4f4f4" />
<v:textbox inset="0,0,0,0">
<![endif]-->
<div style="padding: 40px; color: #ffffff;">
<h1>Hero Content</h1>
</div>
<!--[if mso]>
</v:textbox>
</v:rect>
<![endif]-->
</div>
Provider-Specific Configuration
- Namespace Declaration: Always declare
xmlns:v="urn:schemas-microsoft-com:vml"andxmlns:o="urn:schemas-microsoft-com:office:office"in the<html>tag. Without it, Outlook silently strips VML elements. - Gradient Fallbacks: Outlook cannot render CSS gradients. Use a solid
colorattribute inside<v:fill>matching the gradient's dominant tone. - Dark Mode Override: Wrap VML in
<!--[if !mso]>to serve alternative assets for non-Outlook clients. VML does not inherit system theme colors automatically.
Table Spacing & Padding Normalization
Outlook applies inconsistent default spacing to <td> and <th> elements, often ignoring margin and collapsing padding on nested tables. The industry standard involves setting border-collapse: collapse on parent tables and using cellpadding="0" cellspacing="0" alongside explicit inline padding on inner <td> elements. For a deep dive into resolving collapsed gutters and phantom whitespace, consult How to fix Outlook table spacing issues.
Normalization Pattern
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;">
<tr>
<td style="padding: 24px 0;">
<div style="padding: 24px; background-color: #ffffff;">
<!-- Content -->
</div>
</td>
</tr>
</table>
Debugging Phantom Whitespace
- Margin Ignorance: Outlook ignores
marginon<p>and<div>tags. Always usepaddingon<td>or nested<div>wrappers. - Vertical Gaps: Font sizing in Outlook defaults to
16pxwithline-height: 1.2. Explicitly setfont-size: 0on wrapper<td>elements to eliminate vertical gaps between inline-block elements. - Line-Height Enforcement: Use
mso-line-height-rule: exactlyto force Outlook to respect exactline-heightvalues instead of calculating them dynamically based on font metrics.
Automation & Build Pipeline Integration
Manual patching of Outlook quirks scales poorly in transactional systems. Modern CI/CD pipelines should integrate HTML inliners, MJML compilers, and Litmus/Email on Acid testing APIs. Automating the injection of MSO conditionals and VML blocks reduces deployment friction and ensures consistent rendering across Outlook 365, Outlook for Mac (which uses WebKit), and legacy desktop versions.
Maizzle Build Configuration
// maizzle.config.js
module.exports = {
build: {
posthtml: {
plugins: [
require('posthtml-mso')(), // Auto-injects MSO conditionals
require('posthtml-juice')({ preserveImportant: true })
]
},
tailwindcss: {
config: './tailwind.config.js',
css: './src/css/main.css'
}
}
}
Litmus API Test via curl (Node.js)
The Litmus REST API v3 accepts a POST to /v3/tests with the compiled HTML payload:
const axios = require('axios');
const runLitmusTest = async (html) => {
const payload = {
test_name: "Outlook Render QA",
html_source: html,
test_type: "preview",
clients: ["outlook2016", "outlook2019", "outlook365", "outlookmac"]
};
try {
const response = await axios.post(
'https://api.litmus.com/v3/tests',
payload,
{
headers: {
'Authorization': `Bearer ${process.env.LITMUS_API_KEY}`,
'Content-Type': 'application/json'
}
}
);
console.log(`Test ID: ${response.data.id}`);
} catch (error) {
console.error('Litmus API Error:', error.response?.data || error.message);
}
};
CI/CD Workflow Integration
- Compile: MJML → HTML via
mjmlCLI or Maizzle. - Inline: Run
juiceor@maizzle/cliwithpreserveImportant: trueto prevent Outlook from stripping critical overrides. - Validate: Parse output with
html-validateto catch unclosed MSO tags. - Snapshot: Trigger Litmus/EoA API on PR merge. Fail build if >5% visual regression detected in Outlook clients.
Root Cause: Why the Word Engine Behaves This Way
Every Outlook quirk traces back to one architectural decision: Outlook 2007 replaced the Trident (Internet Explorer) HTML renderer with the Microsoft Word HTML rendering engine, and that engine is still in place for Outlook 2007, 2010, 2013, 2016, 2019, 2021, and the classic desktop builds of Outlook 365 on Windows. Word was never a browser. It is a word processor whose layout model is built around the printed page, the EMU (English Metric Unit) coordinate system, and a fixed 96-DPI assumption that breaks the moment a user runs Windows at 125% or 150% display scaling.
Understanding the engine removes the guesswork:
- No CSS box model for block elements. Word lays out content as if every container were a Word "frame." It has no concept of
max-width,min-width,float,flex,grid,position, ordisplay:inline-blockon a<div>. Width must be declared on a<table>or<td>as an HTML attribute and an inlinewidthstyle, in points or pixels, with noauto. - Points, not pixels, internally. Word converts every pixel value to points at 96 DPI (1px = 0.75pt). When the dimension does not divide cleanly, Word rounds, which is why a
width:600pxtable can render at 601 or 599 device pixels and why hairline gaps appear between adjacent cells. line-heightis recomputed from font metrics. Word ignores your unitless or pixelline-heightand substitutes a value derived from the font's ascent/descent unless you forcemso-line-height-rule:exactly.- Images get a 1.333× DPI multiplier above 96 DPI. On a 120-DPI (125% scaling) machine, Word scales images by 120/96 = 1.25 unless the image carries explicit
width/heightHTML attributes. This is the single most common cause of "my logo is huge in Outlook on a 4K laptop."
These root causes mean the durable fix is always the same shape: declare explicit dimensions as attributes, force exact metrics with mso- properties, and inject table-based scaffolding through MSO conditionals so the modern markup never has to satisfy Word's frame model. Keep this in mind as you wire fixes into your inline CSS automation so they are applied deterministically rather than by hand.
Ghost Tables and the MSO Conditional Pattern Library
The "ghost table" is the workhorse of Outlook development. The technique: hide a fixed-width table scaffold inside <!--[if mso]> comments that only Outlook parses, while modern clients see a fluid <div>. Below is a fuller pattern library with version-specific behavior called out inline.
Centered fixed-width container (the canonical ghost table)
<!--[if mso]>
<table role="presentation" align="center" border="0" cellspacing="0" cellpadding="0" width="600"
style="width:600px;"><tr><td>
<![endif]-->
<!-- Modern clients (Gmail, Apple Mail, iOS Mail, Samsung Email) render this fluid div -->
<div style="max-width:600px;margin:0 auto;">
<!-- content -->
</div>
<!--[if mso]>
</td></tr></table>
<![endif]-->
<!-- Outlook 2016-2021 (Windows): the div's max-width/margin:auto are ignored,
so the ghost table above supplies the 600px width and centering instead. -->
Two-column ghost layout that stacks on mobile
<!--[if mso]>
<table role="presentation" width="600" style="width:600px;" cellpadding="0" cellspacing="0" border="0">
<tr>
<td width="300" valign="top" style="width:300px;">
<![endif]-->
<!--[if !mso]><!-->
<div style="display:inline-block;width:100%;max-width:300px;vertical-align:top;">
<!--<![endif]-->
<!-- Left column content -->
<!--[if !mso]><!--></div><!--<![endif]-->
<!--[if mso]>
</td>
<td width="300" valign="top" style="width:300px;">
<![endif]-->
<!--[if !mso]><!-->
<div style="display:inline-block;width:100%;max-width:300px;vertical-align:top;">
<!--<![endif]-->
<!-- Right column content -->
<!--[if !mso]><!--></div><!--<![endif]-->
<!--[if mso]>
</td></tr></table>
<![endif]-->
<!-- Outlook 2016/2019/365 (Windows): renders two fixed 300px <td> cells side by side.
Gmail / Apple Mail / iOS Mail: render inline-block divs that wrap to full width
under a max-width media query, giving the stacked mobile layout. -->
Spacer that Outlook will actually honor
<!-- Empty <div style="height:24px"> collapses in Outlook 2016-2021. Use a spacer cell. -->
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td height="24" style="height:24px;line-height:24px;font-size:0;mso-line-height-rule:exactly;"> </td>
<!-- Outlook 2007-2021: font-size:0 + mso-line-height-rule:exactly prevents the
from forcing a ~16px minimum row height. -->
</tr>
</table>
Version targeting reference
The conditional expression chooses which Word build sees the block:
<!--[if mso]> ... <![endif]--> <!-- any MSO (Outlook 2000+) -->
<!--[if gte mso 9]> ... <![endif]--> <!-- Outlook 2000 and newer (mso 9 = Office 2000) -->
<!--[if gte mso 12]> ... <![endif]--> <!-- Outlook 2007 and newer (mso 12 = Office 2007) -->
<!--[if mso 16]> ... <![endif]--> <!-- Outlook 2016/2019/2021/365 desktop (mso 16) -->
<!--[if !mso]><!--> ... <!--<![endif]--> <!-- everyone EXCEPT Word-engine Outlook -->
<!-- Outlook for Mac uses WebKit and does NOT match any [if mso]; it falls into the !mso branch. -->
VML in Depth: Backgrounds, Buttons, and DPI
VML (Vector Markup Language) is the only way to render background images and rounded shapes in the Word engine. The namespace declaration on the <html> element is mandatory — omit it and Outlook silently discards every VML element:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:o="urn:schemas-microsoft-com:office:office">
<!-- Outlook 2007-2021 (Windows): without xmlns:v the <v:rect>/<v:fill> are dropped silently. -->
Full-bleed VML background with the bulletproof office namespace declaration
<!--[if gte mso 9]>
<v:background xmlns:v="urn:schemas-microsoft-com:vml" fill="t">
<v:fill type="tile" src="https://cdn.example.com/bg.png" color="#450920"/>
<!-- Outlook 2007-2021 (Windows): color="#450920" is the solid fallback if the tile
fails to load; type="tile" repeats, use type="frame" to stretch to fit. -->
</v:background>
<![endif]-->
For per-section hero backgrounds rather than a page-wide fill, the <v:rect> + <v:textbox> pattern in the production example above is preferred. The complete catalogue of full-width hero patterns lives in the email background images and VML guide.
VML rounded button (Outlook drops border-radius)
<!--[if mso]>
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word"
href="https://example.com/confirm" style="height:44px;v-text-anchor:middle;width:220px;"
arcsize="18%" stroke="f" fillcolor="#A53860">
<w:anchorlock/>
<center style="color:#ffffff;font-family:Arial,sans-serif;font-size:16px;font-weight:bold;">Confirm</center>
<!-- Outlook 2016-2021 (Windows): arcsize is a PERCENT of the shorter side, not px.
v-text-anchor:middle vertically centers; <w:anchorlock/> stops text becoming editable. -->
</v:roundrect>
<![endif]-->
<!--[if !mso]><!-->
<a href="https://example.com/confirm"
style="display:inline-block;background:#A53860;color:#ffffff;border-radius:8px;
padding:13px 28px;font-family:Arial,sans-serif;font-size:16px;font-weight:bold;
text-decoration:none;">Confirm</a>
<!-- Gmail / Apple Mail / iOS Mail / Samsung Email: render the real anchor with border-radius. -->
<!--<![endif]-->
The full reasoning behind this two-track button is covered in the bulletproof email buttons guide.
DPI scaling fix for VML and images
On high-DPI Windows machines Word multiplies VML and unsized images by the scaling factor. Pin the rendering DPI in the <head> so Outlook stops scaling:
<!--[if mso]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
<!-- Outlook 2016-2021 (Windows): forces 96 DPI so images/VML are NOT upscaled
1.25× on 125% display scaling or 1.5× on 150% scaling. -->
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
Always also set explicit width and height HTML attributes (not just CSS) on every <img>; without attributes the DPI multiplier still applies even with the PixelsPerInch override on some 2016 builds.
Provider & Client Constraint Matrix
The behaviors below are what your fixes must satisfy simultaneously. "Word" denotes the Microsoft Word HTML engine.
| Client | Engine | max-width on div |
@media queries |
background-image (CSS) |
border-radius |
mso- props |
Notes |
|---|---|---|---|---|---|---|---|
| Outlook 2016 (Win) | Word | Ignored | Ignored | Ignored (need VML) | Ignored (need VML) | Honored | Adds ~5px table cell spacing; DPI upscales unsized images |
| Outlook 2019 (Win) | Word | Ignored | Ignored | Ignored (need VML) | Ignored (need VML) | Honored | Same engine as 2016; mso 16 |
| Outlook 365 (Win desktop) | Word | Ignored | Ignored | Ignored (need VML) | Ignored (need VML) | Honored | Classic build; new "One Outlook" is WebKit-ish |
| Outlook.com (web) | Blink + remap | Honored | Honored | Honored | Honored | Ignored | Applies dark-mode color remapping via [data-ogsc] |
| Outlook for Mac | WebKit | Honored | Honored | Honored | Honored | Ignored | Falls into the !mso branch |
| Gmail (web) | Blink | Honored | Honored (in <style>) |
Honored | Honored | Ignored | Strips <head>/some <style> on non-Gmail accounts |
| Apple Mail (macOS) | WebKit | Honored | Honored | Honored | Honored | Ignored | Strong CSS support; honors prefers-color-scheme |
| iOS Mail | WebKit | Honored | Honored | Honored | Honored | Ignored | Auto-scales fonts; respects <picture> media |
| Samsung Email | Blink (WebView) | Honored | Honored | Honored | Honored | Ignored | Aggressive dark-mode inversion on some builds |
The practical reading: only the Word-engine column needs MSO/VML scaffolding. Everything else renders the modern markup, which is why every fix must keep the MSO branch invisible to non-MSO clients. Pair this matrix with the dark mode email CSS constraint table when a single template must survive both Word and inversion engines.
Named-Symptom Debugging Reference
Each entry is symptom → root cause → exact fix.
- "There's a thin line / gap between my stacked images." Word inserts whitespace around inline images and applies its ~5px table spacing. Fix: set
display:blockon each<img>, and on the containing<td>addfont-size:0;line-height:0;mso-line-height-rule:exactly;. - "My 600px layout is wider than 600px only in Outlook." Word adds
mso-table-lspace/mso-table-rspace(default ~1.5pt each) around tables. Fix: on every layout table setstyle="mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse;". - "Buttons have a ghost border / outline in Outlook." Word renders default table cell borders. Fix: add
border:0;andborder-collapse:collapse;pluscellspacing="0" cellpadding="0"as attributes, and use the VMLroundrectfor the shape. - "Background color bleeds past my container." A
<td>with a background but no explicit width stretches to the table width in Word. Fix: declarewidthas both attribute and inline style on the cell, and wrap in a ghost table. - "Text is double-spaced in Outlook but single in Gmail." Word recomputed
line-heightfrom font metrics. Fix:mso-line-height-rule:exactly;line-height:24px;on the text cell. - "My web font falls back to Times New Roman in Outlook." Word ignores
@font-faceand falls back to its default serif. Fix: declare a safe fallback stack (font-family:'Inter',Arial,Helvetica,sans-serif;) — Arial, not the browser default. For the Apple-Mail web-font path see web font fallbacks for Apple Mail. - "Images are 25% too big on a high-DPI laptop." Word applied the DPI multiplier to unsized images. Fix: add
width/heightHTML attributes and thePixelsPerInch96 override above. - "My CTA link text turned blue and underlined in Outlook." Word applies its default hyperlink style. Fix: wrap link text in a
<span style="color:#ffffff;text-decoration:none;">and addstyle="text-decoration:none;color:#ffffff;"on the<a>itself. - "Padding on a
<p>disappears." Word ignores margin/padding on<p>and<div>. Fix: move spacing topaddingon a wrapping<td>. - "My MSO fix shows up in Gmail too." A comment was not closed or used the wrong polarity. Fix: confirm
<!--[if mso]>...<![endif]-->for Outlook-only and<!--[if !mso]><!-->...<!--<![endif]-->for everyone-else-only; validate with an HTML linter.
For the table-spacing family specifically, the dedicated how to fix Outlook table spacing issues reference walks through each measurement.
Numbered Pipeline Integration Steps
To make these fixes deterministic rather than artisanal, fold them into the build so no engineer hand-writes a ghost table again:
- Author modern. Write templates in MJML or React Email with semantic, fluid markup — no MSO comments by hand. MJML emits ghost tables and VML buttons automatically for its
mj-section,mj-column, andmj-buttoncomponents. - Compile. Run
mjml input.mjml -o output.html(or the React Email render step) in CI. This is where thexmlns:v/xmlns:onamespaces andPixelsPerInchhead block are injected. - Inline. Pipe through
juicewithpreserveImportant:trueand conditional-comment preservation enabled, so the inliner does not strip<!--[if mso]>blocks. Configure this in your inline CSS automation stage. - Lint. Run
html-validate(orhtmlhint) to catch unclosed conditionals and stray VML namespaces before they reach a client. - Snapshot render. POST the compiled HTML to Litmus/Email on Acid for
outlook2016,outlook2019,outlook365, andoutlookmac; fail the build on visual regression beyond your threshold. - Gate the merge. Block the PR if any Outlook client shows a regression, then dispatch only the approved artifact to your ESP.
// ci-outlook-gate.js — fail CI if Outlook screenshots regress
const fs = require('fs');
const { runLitmusTest } = require('./litmus'); // see the Litmus snippet above
(async () => {
const html = fs.readFileSync('dist/welcome.html', 'utf8');
// Guard: refuse to ship if the VML namespace was lost during inlining.
if (!/xmlns:v=["']urn:schemas-microsoft-com:vml/.test(html)) {
throw new Error('VML namespace missing — Outlook backgrounds/buttons will be dropped.');
}
// Guard: refuse to ship if conditional comments were stripped by the inliner.
if (!/<!--\[if mso\]>/.test(html)) {
throw new Error('MSO conditional comments stripped — ghost tables are gone.');
}
await runLitmusTest(html); // Outlook 2016/2019/365/Mac snapshot
})();
Validation Checklist
xmlns:vandxmlns:onamespaces declared on the<html>element<o:PixelsPerInch>96</o:PixelsPerInch>MSO head block present to stop DPI upscaling- Every
<img>has explicitwidthandheightHTML attributes (not just CSS) - All layout tables set
mso-table-lspace:0pt;mso-table-rspace:0pt;border-collapse:collapse; - Fixed-width sections wrapped in a ghost
<table>inside<!--[if mso]> - Text cells carry
mso-line-height-rule:exactlywith an explicitline-height - Buttons use a
<v:roundrect>for Outlook plus a real<a>in the!msobranch - Background images supplied via
<v:fill>/<v:background>with a solidcolorfallback - Conditional comments survive the CSS inliner (verified post-build, not just in source)
- HTML linter reports zero unclosed
<![endif]-->or stray VML namespaces - Litmus/Email on Acid snapshots captured for Outlook 2016, 2019, 365, and Mac
Conclusion
Engineering for Outlook requires a shift from web-first paradigms to email-specific constraints. By leveraging conditional targeting, VML fallbacks, and automated testing pipelines, development teams can deliver pixel-perfect experiences without compromising modern CSS elsewhere. Integrating these patterns into your transactional email infrastructure ensures deliverability, accessibility, and brand consistency across Microsoft's fragmented client ecosystem.
Frequently Asked Questions
Does the new "One Outlook" (Outlook for Windows, 2024+) still use the Word engine?
No. The new unified Outlook client is built on the web-based Outlook.com codebase (Edge WebView2), so it honors max-width, media queries, and border-radius. But the classic Outlook 2016/2019/2021 and the classic desktop 365 build remain widely deployed in enterprises, so you must keep the Word-engine fixes until your analytics show the legacy share is negligible.
Can I just skip MSO conditionals and let Outlook render the fluid layout degraded?
Only if a single-column, fixed-width fallback is acceptable. Without a ghost table, Word will collapse max-width divs to either full window width or content width unpredictably. For transactional mail where layout integrity matters, the ghost table is cheap insurance.
Why do my conditional comments work locally but disappear after sending?
Many ESPs and CSS inliners strip HTML comments by default. Disable comment stripping in your ESP template settings and configure juice/your inliner to preserve conditional comments, then verify in the received message source, not just the pre-send preview.
Is VML deprecated — should I avoid it?
VML is legacy, but the Word engine still depends on it and there is no replacement within that engine. Continue using VML strictly inside <!--[if mso]> so no modern client ever parses it; everyone else gets standard CSS.
Related
- How to fix Outlook table spacing issues — resolve collapsed gutters and phantom whitespace in nested tables.
- Email Background Images & VML — the complete VML pattern for full-bleed hero imagery in Outlook.
- Dark Mode Email CSS — pair VML color overrides with explicit dark-mode hex values.
- Inline CSS Automation — preserve MSO conditionals and VML through the build pipeline.
← Back to Mastering Email HTML & CSS