Recursive AI

Convert an RTF email body to HTML in C#

All onemail recipes

Read the message’s RtfBody and pass it to RtfHtmlConverter.Convert: you get HTML and plain text, with no Word or Outlook. Many Outlook messages, notes and tasks carry only an RTF body. For those, the read view already does this, and HtmlBody.Origin says RtfConverted. The program below converts the RTF bodies of two Apache POI test messages: one written as RTF, and one in Cyrillic (code page 1251) that has only text and RTF.

Terminal
dotnet new console -o RtfToHtml && cd RtfToHtml
dotnet add package RecursiveAI.OneMail --version 0.3.0-preview.2
curl -LO https://raw.githubusercontent.com/apache/poi/REL_5_4_1/test-data/hsmf/quick.msg
curl -LO https://raw.githubusercontent.com/apache/poi/REL_5_4_1/test-data/hsmf/cyrillic_message.msg

Put this in Program.cs and run dotnet run:

C#
using RecursiveAI.OneMail;
using RecursiveAI.OneMail.Rtf;
 
foreach (string path in new[] { "quick.msg", "cyrillic_message.msg" })
{
    using MailContainer file = Mail.Open(path);
    IMailItem msg = file.Enumerate().First();
    Console.WriteLine($"{path}: HTML body {msg.HtmlBody?.Origin.ToString() ?? "none"}");
    if (msg.RtfBody is null) continue;
 
    // RtfBody is the decompressed RTF; the converter never throws on bad input
    using var rtf = new MemoryStream();
    using (Stream s = msg.RtfBody.Open()) s.CopyTo(rtf);
    RtfHtmlResult result = RtfHtmlConverter.Convert(rtf.ToArray());
 
    string html = Path.ChangeExtension(path, ".html");
    File.WriteAllText(html, result.Html);
    Console.WriteLine($"  {html}: {result.Html.Length:N0} characters, code page {result.DefaultCodePage}, {result.Defects.Count} defects");
    Console.WriteLine($"  {result.Text.Split('\n')[0].Trim()}");
}
Text
quick.msg: HTML body RtfConverted
  quick.html: 173 characters, code page 1252, 0 defects
  The quick brown fox jumps over the lazy dog
cyrillic_message.msg: HTML body none
  cyrillic_message.html: 874 characters, code page 1251, 0 defects
  Ваше сообщение с темой "FW: RTM" было успешно получено брокером подсистемы обмена данными.

cyrillic_message.html holds the automatic reply, decoded from code page 1251:

HTML
<div class="rtf-body" style="font-family:'Arial',sans-serif;font-size:10pt">
<p style="margin-top:0;margin-bottom:0">Ваше сообщение с темой &quot;FW: RTM&quot; было успешно получено брокером подсистемы обмена данными.</p>
<p style="margin-top:0;margin-bottom:0">&nbsp;&nbsp;В нем были обнаружены следующие вложения:</p>
<p style="margin-top:0;margin-bottom:0">10595000.58B</p>
<p style="margin-top:0;margin-bottom:0">10595000.56B</p>
<p style="margin-top:0;margin-bottom:0">10595000.59B</p>
<p style="margin-top:0;margin-bottom:0">10595000.57B</p>
<p style="margin-top:0;margin-bottom:0"><br></p>
<p style="margin-top:0;margin-bottom:0">Сообщение передано в обработку 18.05.2010 11:38:39</p>
<p style="margin-top:0;margin-bottom:0">------------</p>
<p style="margin-top:0;margin-bottom:0">&nbsp;Сообщение сгенерировано автоматически, не следует отвечать на него.</p>
</div>

What converts

Paragraphs, headings from the stylesheet, bold, italic and the other run styles, fonts and colours, bulleted and numbered lists, tables, hyperlinks and pictures. Pictures become data: URIs by default; set new RtfHtmlOptions { ImageMode = RtfImageMode.ContentId } to get cid: links and the bytes in result.Images. result.Text is the same document as plain text. The output is one <div class="rtf-body"> fragment, and the same input always gives the same HTML.

The project compared the converter with LibreOffice, pandoc and unrtf on 123 documents, by words, styles, lists, tables, links and headings, with no unexplained difference.

Limits

  • A preview. 0.3.0-preview.2: the API can change before 1.0.
  • Not sanitised. The HTML comes from the sender’s RTF. Treat it like any HTML from an email before you show it in a browser. OneMail’s display sanitiser (ToDisplayHtml) has not had its independent security review yet: don’t rely on it to make untrusted mail safe.
  • Placeholders. EMF and WMF pictures and the positions of attachments inside the RTF (\objattph) become placeholders.
  • Limits on size. Group depth, node count, output length and picture sizes are capped (RtfHtmlOptions); a hit is a typed defect, and the output so far is kept.

How the converter works, and how it was checked: Compressed RTF and RTF to HTML. Reading the rest of the message: Read an Outlook MSG file.