Skip to main content

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.

Word-engine quirks map Each Outlook Word-engine quirk maps to a specific mitigation: ghost tables, VML fills, MSO line-height, and spacing resets. Word-Engine Quirks & Fixes Quirk: ignores max-width Fix: MSO ghost table <!--[if mso]> Quirk: no background-image Fix: VML v:rect / v:fill <v:fill src> Quirk: stretches line-height Fix: mso-line-height-rule exactly Quirk: ignores margin Fix: padding on td cellpadding="0" Quirk: 5px cell spacing Fix: mso-table-lspace 0pt / 0pt Quirk: drops rounded CSS Fix: VML roundrect <v:roundrect> Every quirk is conditional — wrap fallbacks in MSO comments so other clients never see them.
The Word-engine quirks map: each Outlook rendering bug pairs with a specific MSO-conditional or VML mitigation that stays invisible to WebKit and Blink clients.

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 .mhtml or 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" and xmlns: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 color attribute 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 margin on <p> and <div> tags. Always use padding on <td> or nested <div> wrappers.
  • Vertical Gaps: Font sizing in Outlook defaults to 16px with line-height: 1.2. Explicitly set font-size: 0 on wrapper <td> elements to eliminate vertical gaps between inline-block elements.
  • Line-Height Enforcement: Use mso-line-height-rule: exactly to force Outlook to respect exact line-height values 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

  1. Compile: MJML → HTML via mjml CLI or Maizzle.
  2. Inline: Run juice or @maizzle/cli with preserveImportant: true to prevent Outlook from stripping critical overrides.
  3. Validate: Parse output with html-validate to catch unclosed MSO tags.
  4. 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, or display:inline-block on a <div>. Width must be declared on a <table> or <td> as an HTML attribute and an inline width style, in points or pixels, with no auto.
  • 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:600px table can render at 601 or 599 device pixels and why hairline gaps appear between adjacent cells.
  • line-height is recomputed from font metrics. Word ignores your unitless or pixel line-height and substitutes a value derived from the font's ascent/descent unless you force mso-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/height HTML 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;">&nbsp;</td>
    <!-- Outlook 2007-2021: font-size:0 + mso-line-height-rule:exactly prevents the
         &nbsp; 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:block on each <img>, and on the containing <td> add font-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 set style="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; and border-collapse:collapse; plus cellspacing="0" cellpadding="0" as attributes, and use the VML roundrect for 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: declare width as 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-height from 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-face and 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/height HTML attributes and the PixelsPerInch 96 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 add style="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 to padding on 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:

  1. 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, and mj-button components.
  2. Compile. Run mjml input.mjml -o output.html (or the React Email render step) in CI. This is where the xmlns:v/xmlns:o namespaces and PixelsPerInch head block are injected.
  3. Inline. Pipe through juice with preserveImportant:true and conditional-comment preservation enabled, so the inliner does not strip <!--[if mso]> blocks. Configure this in your inline CSS automation stage.
  4. Lint. Run html-validate (or htmlhint) to catch unclosed conditionals and stray VML namespaces before they reach a client.
  5. Snapshot render. POST the compiled HTML to Litmus/Email on Acid for outlook2016, outlook2019, outlook365, and outlookmac; fail the build on visual regression beyond your threshold.
  6. 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:v and xmlns:o namespaces declared on the <html> element
  • <o:PixelsPerInch>96</o:PixelsPerInch> MSO head block present to stop DPI upscaling
  • Every <img> has explicit width and height HTML 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:exactly with an explicit line-height
  • Buttons use a <v:roundrect> for Outlook plus a real <a> in the !mso branch
  • Background images supplied via <v:fill>/<v:background> with a solid color fallback
  • 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.


← Back to Mastering Email HTML & CSS