Two silent failures in Outlook .msg parsing: empty body and dropped embedded messages

Real Outlook-exported `.msg` files exposed two bugs in easterbrooka's extractor: the message body came back empty, and emails forwarded as attachments were silently dropped. Both are fixed here.

intakediscovery

The body loss was quiet: Outlook composes most mail in HTML, so the plain-text property on .msg files is typically undefined. The extractor was reading only that property and returning an empty string when it wasn't set. The fix falls back to HTML-stripped content when plain text is absent, matching the behaviour that was already in place for .eml ingestion. Plain text still takes precedence when both representations exist. RTF-compressed-only bodies are explicitly deferred - no real example has surfaced yet.

The dropped attachments bug was structural. Emails forwarded as attachments inside a .msg file are flagged with innerMsgContent by the msgreader library, and their display name comes from a different property than regular file attachments. The library synthesises the .msg extension at access time. The extractor's attachment filter only looked at the standard filename property, so inner-message entries were filtered out entirely - they never appeared in the UI's attachment chip list and the LLM's recursive expansion pass never saw them. The fix checks for the innerMsgContent marker, synthesises the filename the library would have produced, and lets the entry flow through both surfaces.

Six new vitest cases cover the HTML fallback, plain-text priority, the no-body case, inner-message listing with and without a display name, and recursive inner-message expansion inside the LLM payload builder. The backend suite stayed at full green and TypeScript compiled clean before merge.

So what Worth pulling if you handle Outlook-exported `.msg` files and have noticed blank message bodies or missing forwarded-message attachments. The test coverage is thorough for both scenarios. No API or schema changes; this is entirely within the extractor layer.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?