Skip to content

AnsCodeLab/OutlookExportMarkdown

Repository files navigation

OutlookExportMarkdown

A VSTO Outlook Add-in (C# / .NET Framework 4.8) that exports email threads to self-contained Markdown files. Inline images are embedded as Base64 data URIs, quoted reply blocks are stripped, and the entire thread is written as a single UTF-8 .md file.


Features

  • Export Email — exports the currently selected or open email as a standalone Markdown document.
  • Export Thread — collects every message in the conversation via the Outlook Conversation API and exports the full thread, newest message first, matching Outlook's default sort order.
  • Split-button ribbon control — appears in the Export group on the Home tab (after the Respond group) in both the folder view and the message inspector window.
  • Inline image embeddingcid: image references are resolved from inline attachments and replaced with data:image/...;base64,... URIs so the exported file is fully self-contained.
  • Office HTML sanitisation — Office namespace tags (<o:p>, <w:...>, <m:...>) are removed before HTML-to-Markdown conversion.
  • Quoted reply stripping — Outlook quoted-reply sections are removed before conversion so thread emails are not duplicated. Three Outlook reply patterns are handled:
    • <div id="divRplyFwdMsg"> (modern Outlook)
    • <div style="border-top ..."> containing a From: header (older Outlook)
    • <hr> followed by an inline bold From: / Sent: block (plain-text style replies)
    • <blockquote> elements (forwarded or quoted content)
  • Plain-text fallback — if HTML conversion produces empty output, the add-in falls back to MailItem.Body (plain text).
  • Safe file naming — the email subject is sanitised (invalid filename characters replaced with _, truncated to 100 characters) and used as the default filename in the Save dialog.

Output Format

Each email in the exported file renders as the block below. A top-level # Subject heading precedes the first block.

# Project Update

---

## From: Jane Smith <jane@example.com>
**Date:** 2026-05-20 14:32  
**To:** John Doe <john@example.com>  
**CC:** team@example.com  
**Subject:** Re: Project Update

Email body text converted from HTML.

![logo](data:image/png;base64,iVBORw0KGgo...)

The CC: line is omitted when the field is empty. Thread emails are separated by --- horizontal rules.


Requirements

Requirement Details
Operating system Windows 10 or Windows 11
Outlook Outlook 2016, 2019, 2021, or Microsoft 365 Desktop (Windows)
.NET .NET Framework 4.8 (included with Windows 10 1903 and later)
Development only Visual Studio 18 (2025 Insiders) with the Office/SharePoint development workload

The dotnet CLI cannot build VSTO projects. MSBuild via Visual Studio is required.


Installation

Close Outlook before running the installer.

# Run from the repository root in an unrestricted PowerShell session
.\Install.ps1

The script performs four steps:

  1. Builds the Release configuration using MSBuild (located automatically under C:\Program Files\Microsoft Visual Studio).
  2. Copies OutlookExportMarkdown.dll, OutlookExportMarkdown.dll.manifest, OutlookExportMarkdown.vsto, HtmlAgilityPack.dll, ReverseMarkdown.dll, and the two VSTO utility DLLs to %APPDATA%\OutlookExportMarkdown.
  3. Writes the add-in registration keys to HKCU\SOFTWARE\Microsoft\Office\Outlook\Addins\OutlookExportMarkdown with LoadBehavior set to 3 (load at startup).
  4. Adds a VSTO trust entry under HKCU\SOFTWARE\Microsoft\VSTO\Security\Inclusion so Outlook loads the unsigned manifest without a prompt.

After installation completes, restart Outlook. The Export group appears on the Home tab in the ribbon.

Uninstall

.\Uninstall.ps1

Removes the registry add-in entry, the VSTO trust entry, and the %APPDATA%\OutlookExportMarkdown folder. Restart Outlook after uninstalling.


Usage

Export a single email

  1. Select or open an email in Outlook.
  2. On the Home tab, click the Export Email split-button in the Export group.
  3. Choose a save location in the dialog. The email subject is pre-filled as the filename.

Export an entire thread

  1. Select or open any email in the conversation.
  2. Click the dropdown arrow on the split-button, then choose Export Thread.
  3. Choose a save location. All messages in the thread are collected and written to a single .md file, newest first.

The Export Thread menu item is disabled (greyed out) when no email is selected. The button state re-evaluates on every selection change.


Project Structure

OutlookExportMarkdown/
├── OutlookExportMarkdown.sln
├── Install.ps1                          — one-command installer
├── Uninstall.ps1                        — one-command uninstaller
│
├── OutlookExportMarkdown/               — VSTO add-in project (.NET Framework 4.8)
│   ├── OutlookExportMarkdown.csproj
│   ├── ThisAddIn.cs                     — VSTO entry point; wires ribbon via
│   │                                      CreateRibbonExtensibilityObject
│   ├── ThisAddIn.Designer.cs            — VSTO-generated partial class (do not edit)
│   ├── ExportRibbon.cs                  — IRibbonExtensibility callbacks
│   ├── ExportRibbon.xml                 — CustomUI XML (Embedded Resource)
│   ├── Models/
│   │   ├── EmailData.cs                 — plain data model (no COM types)
│   │   └── AttachmentData.cs            — inline image bytes and MIME type
│   ├── Core/
│   │   ├── ConversationCollector.cs     — Outlook COM → List<EmailData>
│   │   ├── EmailToMarkdown.cs           — List<EmailData> → Markdown string
│   │   ├── ImageExtractor.cs            — List<AttachmentData> → content-id → data URI map
│   │   └── FileExporter.cs             — SaveFileDialog + UTF-8 file write
│   └── Properties/
│       └── AssemblyInfo.cs
│
└── OutlookExportMarkdown.Tests/         — MSTest unit test project (.NET Framework 4.8)
    ├── OutlookExportMarkdown.Tests.csproj
    └── Core/
        ├── EmailToMarkdownTests.cs      — 14 tests
        └── ImageExtractorTests.cs       — 5 tests

Architecture

The add-in is structured in three layers.

┌──────────────────────────────────────────────┐
│          Outlook Integration Layer           │
│  ExportRibbon.cs / ExportRibbon.xml          │
│  ThisAddIn.cs                                │
├──────────────────────────────────────────────┤
│            Conversion Engine                 │
│  ConversationCollector  EmailToMarkdown      │
│  ImageExtractor                              │
├──────────────────────────────────────────────┤
│              Export Layer                    │
│  FileExporter  (SaveFileDialog, UTF-8 write) │
└──────────────────────────────────────────────┘

Outlook Integration LayerExportRibbon.cs implements IRibbonExtensibility and loads ribbon XML from the embedded resource ExportRibbon.xml. Ribbon callbacks retrieve the selected MailItem from either the active Explorer (folder view) or the active Inspector (message window). ThisAddIn.cs returns the ribbon instance from CreateRibbonExtensibilityObject.

Conversion EngineConversationCollector calls MailItem.GetConversation(), walks the Conversation.GetTable() row set, retrieves each MailItem by EntryID, and returns a List<EmailData> sorted by SentOn descending. EmailToMarkdown parses the HTML body via HtmlAgilityPack, strips Office namespace nodes and quoted-reply sections, resolves cid: image references via ImageExtractor, then converts the sanitised HTML to Markdown using ReverseMarkdown.

Export LayerFileExporter presents a SaveFileDialog with the sanitised subject as the default filename, then writes the Markdown string as UTF-8. Write errors are reported via MessageBox.

The data models (EmailData, AttachmentData) contain no COM types, which is what makes the conversion engine fully unit-testable without a live Outlook instance.

FindRibbons override

The project overrides the VSTO FindRibbons MSBuild task with a no-op. The task ordinarily loads the add-in DLL via Assembly.Load to discover designer-based ribbons; this fails in MSBuild CLI builds. Because the add-in uses an XML ribbon (IRibbonExtensibility), RibbonTypes is always empty and the no-op is safe.


Development

Build

Open OutlookExportMarkdown.sln in Visual Studio 18 (2025 Insiders) and press Ctrl+Shift+B, or invoke MSBuild directly:

& "C:\Program Files\Microsoft Visual Studio\18\Insiders\MSBuild\Current\Bin\MSBuild.exe" `
    OutlookExportMarkdown\OutlookExportMarkdown.csproj `
    /p:Configuration=Release /verbosity:minimal

The dotnet build command does not support the VSTO project type ({BAA0C2D2-18E2-41B9-852F-F413020CAA33}). Use MSBuild only.

Debug

Press F5 in Visual Studio. VSTO launches Outlook with the add-in loaded from the Debug output folder. The DebugInfoExeName project property points to outlook.exe under HKLM\SOFTWARE\Microsoft\Office\16.0\Outlook\InstallRoot\Path.

Run tests

Build the test project first (Debug configuration), then run:

& "C:\Program Files\Microsoft Visual Studio\18\Insiders\Common7\IDE\CommonExtensions\Microsoft\TestWindow\vstest.console.exe" `
    OutlookExportMarkdown.Tests\bin\Debug\OutlookExportMarkdown.Tests.dll

15 of 19 tests execute via the CLI. The remaining 4 tests require additional context that is only available inside the Visual Studio Test Explorer runner. To run all 19 tests, use Test → Run All Tests in Visual Studio.

Test coverage:

Class Tests
EmailToMarkdown 14
ImageExtractor 5
Total 19

ConversationCollector and FileExporter depend on the Outlook COM object model and System.Windows.Forms.SaveFileDialog respectively; they are verified by manual smoke test rather than automated unit tests.


Dependencies

Package Version Purpose
HtmlAgilityPack 1.11.61 Parse Outlook HTML bodies into a traversable DOM
ReverseMarkdown 4.6.0 Convert sanitised HTML nodes to Markdown
MSTest.TestFramework 3.1.1 Unit test framework
MSTest.TestAdapter 3.1.1 VSTest adapter for MSTest

Error Handling Reference

Scenario Behaviour
No email selected Export Thread menu item is disabled; re-evaluated on SelectionChange
Email has no conversation ConversationCollector falls back to exporting the single MailItem
Inline image extraction fails ![image unavailable]() placeholder is inserted; conversion continues
HTML conversion returns empty output Falls back to MailItem.Body (plain text)
Save dialog cancelled Export aborts silently; no file is written
File write error MessageBox shown with the attempted path and exception message

Limitations

  • Windows only. Outlook on Mac, Outlook Web, and the new Outlook for Windows (web-based) are not supported.
  • Non-inline file attachments (PDFs, Office documents, etc.) are not exported.
  • Batch export of multiple unrelated threads in a single operation is not supported.
  • The installer uses a self-signed temporary key (OutlookExportMarkdown_TemporaryKey.pfx). Enterprise deployments may require a code-signing certificate trusted by the organisation's Group Policy.

License

No license file is present in this repository. All rights reserved by the author.

About

VSTO Outlook Add-in that exports email threads to Markdown files with Base64-embedded inline images

Resources

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors