While it doesn't look that complicated in the first place, properly reading an Microsoft.Exchange.Data.Transport.Email.EmailMessage Body isn't that straightforward: some characters like tabulations (\t) can easily be missing.
My original approach was the following:
public static string GetStringBody(this IEmailBody body)
{
using var bodyStream = body.GetContentReadStream();
using var ms = new MemoryStream();
bodyStream.CopyTo(ms);
var bytes = ms.ToArray();
return Encoding.Unicode.GetString(bytes);
}Which I would call in this context:
namespace MyNS
{
public class MyAgent : SmtpReceiveAgent
{
public MyAgent()
{
OnEndOfData += (source, e) =>
{
// here
var stringBody = e.MailItem.Message.Body.GetStringBody();
}
}
}
}This mostly works, but for some reason, some characters like tabs (\t) and maybe others were missing, despite being present in the original message as well as in the received message on the other client. I was just not able to properly read the body inside the plugin.
Microsoft being Microsoft, the message passed to the plugins was encoded in a special format called TNEF. It was visible in the Content-Type header when dumping the whole message, and we could also notice here that the body was (the only thing) encoded and not readable as-is.
To properly read the body, we need to decode using TNEF functions.
I wish this hell to nobody, so here's the full snippet to read the body:
using Microsoft.Exchange.Data.ContentTypes.Tnef; // in Microsoft.Exchange.Data.Common.dll
/// <remarks>
/// Pass <c>e.MailItem.Message</c> to this function.
/// </remarks>
public static string GetStringBodyFromMessage(EmailMessage message)
{
if (message.TnefPart == null)
{
using var rawContentStream = message.RootPart.GetRawContentReadStream();
using var rawReader = new StreamReader(rawContentStream);
return rawReader.ReadToEnd();
}
try
{
using var tnefStream = message.TnefPart.GetContentReadStream();
using var tnefReader = new TnefReader(tnefStream);
// Read TNEF content
while (tnefReader.ReadNextAttribute())
{
if (tnefReader.AttributeLevel == TnefAttributeLevel.Message && tnefReader.AttributeTag == TnefAttributeTag.MapiProperties)
{
while (tnefReader.PropertyReader.ReadNextProperty())
{
var propertyTag = tnefReader.PropertyReader.PropertyTag;
switch (propertyTag.ValueTnefType)
{
case TnefPropertyType.String8:
case TnefPropertyType.Unicode:
if (Array.Exists(BodyPropertyIds, id => id == propertyTag.Id))
{
return propertyReader.ReadValueAsString();
}
break;
}
}
}
}
}
catch (Exception ex)
{
throw new Exception($"Error extracting TNEF body: {ex.Message}");
}
return string.Empty;
}
private static readonly TnefPropertyId[] BodyPropertyIds = new TnefPropertyId[]
{
TnefPropertyId.Body, // PR_BODY
TnefPropertyId.BodyHtml, // PR_HTML_BODY
TnefPropertyId.RtfCompressed, // PR_RTF_COMPRESSED
};