Why most Outlook messages have no HTML body

Measured against 20 real business .msg files: 17 stored their body only as compressed RTF. What that means, why Outlook does it, and where naive readers break.

If you open a .msg file expecting to find HTML in it, most of the time you will not. Outlook stores the body in up to three different places, and the one everybody looks for first is usually empty. This is the single biggest reason .msg viewers render blank pages.

The measurement

Against a corpus of 20 real business .msg files — offers, invoices, quotations, forwarded threads, the ordinary contents of a working mailbox — 17 of them had no HTML body at all. The formatted message existed only as compressed RTF.

That is 85%. A viewer that reads PidTagHtml, finds nothing, and gives up will render an empty page for more than four files in five. This is not an edge case; it is the normal case, and it is why so many .msg tools look broken on real mail while working perfectly on a test message.

The number is worth stating precisely because it is so rarely stated at all. Most documentation describes the three body properties as alternatives without saying which one you will actually encounter.

The three places a body can live

A .msg file is a Compound File Binary container — the same OLE2 structure Office used for .doc and .xls — holding MAPI properties as separate internal streams. The body may appear in three of them:

  • PidTagHtml (0x1013) — the HTML body as raw bytes. This is the one everybody reads. It is frequently absent.
  • PidTagRtfCompressed (0x1009) — the body as RTF, compressed. Present on almost every message that has any formatting at all.
  • PidTagBody (0x1000) — plain text. Nearly always present, and nearly always a poor substitute: tables collapse, emphasis disappears, and a quoted reply chain becomes an undifferentiated wall.

The correct fallback order is HTML, then RTF de-encapsulated back to HTML, then plain text. Skipping the middle step is what costs you 85% of your fidelity.

What “compressed RTF” actually means

The compression is not gzip or deflate. It is a Microsoft-specific LZ77 variant described in MS-OXRTFCP, and its most unusual feature is that it starts from a preloaded dictionary — a 207-byte string of common RTF control words such as \viewkind, \par and \pard.

Because those tokens are already in the window before the first byte of your message is read, a short RTF document compresses far better than a general-purpose algorithm would manage. It also means you cannot decompress the stream with any standard tool: without that exact dictionary, the back-references at the start of the stream point at nothing.

A stream can also be stored uncompressed, signalled by the magic value MELA rather than LZFu in its header. Readers that assume compression unconditionally fail on those.

The part that surprises people: the RTF contains HTML

When Outlook sends an HTML message, it does not throw the HTML away and re-author it as RTF. It wraps it. The RTF stream carries the original HTML inside it, marked up so that an RTF reader and an HTML reader each see what they need. The mechanism is MS-OXRTFEX, and it works through three devices:

  • \fromhtml1 in the header, declaring that this RTF is encapsulated HTML rather than native RTF.
  • {\*\htmltag<N> ... } destinations, each holding a fragment of the original HTML source verbatim. The number encodes what kind of fragment it is.
  • \htmlrtf / \htmlrtf0 toggles, which bracket RTF that exists only so RTF readers see something sensible. An HTML de-encapsulator must ignore everything between them.

Reassembling the HTML means walking the RTF, emitting the contents of the htmltag destinations, honouring the toggles, and decoding the character escapes — \'hh for a byte in the declared code page, \uN for a Unicode code point followed by N fallback characters to skip. Get the skip count wrong and every non-Latin message fills with stray characters.

A trap worth knowing about

In HTML de-encapsulation, a bare \par is RTF-side layout. The real paragraph structure is already in the <p> tags carried by the htmltag destinations, so emitting a line break for \par duplicates it. The correct behaviour is to drop it.

Except inside an \*\htmltag destination, where it means the opposite. There it encodes a newline that was present in the original HTML source — Outlook writes {\*\htmltag4 \par } for one. HTML collapses that newline to a space. Drop it and adjacent words weld together.

The symptom is unmistakable once you have seen it: a sentence reading “the prevailing9% GST”, where the source had a line break between prevailing and 9%. Two rules for the same control word, decided by context.

The other half of the fidelity problem

Recovering the HTML is not the end of it. Outlook writes its formatting into a <style> block in <head>, not into inline style attributes on each element.

Any viewer that sanitises the message and keeps only the <body> fragment — which is the default behaviour of most HTML sanitisers, including DOMPurify — silently discards that stylesheet. The text survives; the table borders, fonts, colours and spacing do not. On a real message this is the difference between a formatted quotation table and an unstyled list of numbers.

It is a quiet failure. Nothing errors, nothing is missing from the text, and the result looks plausible until you compare it against Outlook side by side.

How to check your own files

A .msg is a compound file, so any OLE2 browser will list its streams. The body properties appear as __substg1.0_1013 (HTML), __substg1.0_1009 (compressed RTF) and __substg1.0_1000 (plain text), with a suffix indicating the data type.

If 1013 is absent and 1009 is present, you have one of the 85%, and any tool that renders your message correctly is doing the RTF work described above.

What this means when choosing a viewer

Two questions separate a tool that works on real mail from one that works on test messages:

  • Does it de-encapsulate compressed RTF? Open a message you know has formatting. If the body is blank or plain, it does not.
  • Does it keep the message’s own stylesheet? Open something with a table. If the borders are gone, it dropped the <head><style>.

Both are checkable in about thirty seconds with a file you already have. The viewer on this site does both, which is why it was built — what a .msg file is covers the container format in more detail, and the troubleshooting guide covers the failures that are not about the body at all.

Open a .msg file right now

No sign-up, no upload, no software to install.

Open the viewer