Introduction

Parsing email messages programmatically is a common requirement for archival tools, compliance pipelines, and migration utilities. Outlook MSG files use the Compound File Binary (CFB) container format with MAPI property streams, while EML files follow the MIME standard. Both formats require careful handling of encodings, folded headers, and nested attachments.

Aspose.Email FOSS for .NET is a free, MIT-licensed C# library that provides a complete read-write API for MSG and EML messages. It targets .NET 8.0+ and has no dependency on Microsoft Outlook or any native COM interop layer. The library is available as the aspose.email.foss NuGet package.

This post walks through the message-parsing capabilities included in the library, from loading files off disk to extracting individual MAPI properties and converting between formats.


What’s Included

Loading MSG Files

The MapiMessage class is the primary entry point for working with Outlook MSG files. You can load a message from a file path or a stream, with an optional strict-validation flag that surfaces any structural issues found in the file.

// Load an MSG file from disk
var message = MapiMessage.FromFile("meeting-invite.msg");

Console.WriteLine($"Subject: {message.Subject}");
Console.WriteLine($"From: {message.SenderName} <{message.SenderEmailAddress}>");
Console.WriteLine($"Date: {message.MessageDeliveryTime}");
Console.WriteLine($"Recipients: {message.Recipients.Count}");
Console.WriteLine($"Attachments: {message.Attachments.Count}");

The returned MapiMessage exposes typed properties for common fields including Subject, Body, HtmlBody, SenderName, SenderEmailAddress, InternetMessageId, and MessageDeliveryTime.

Loading EML Messages

EML files use the MIME format. MapiMessage.LoadFromEml accepts a file path, a Stream, or a byte[] and returns the same MapiMessage object, so you use one unified API regardless of the source format.

// Load from a file path
var fromFile = MapiMessage.LoadFromEml("newsletter.eml");

// Load from a stream
using var stream = File.OpenRead("newsletter.eml");
var fromStream = MapiMessage.LoadFromEml(stream);

Console.WriteLine($"Subject: {fromStream.Subject}");
Console.WriteLine($"Body: {fromStream.Body?.Trim()}");

The parser handles folded headers, Base64 and quoted-printable transfer encodings, and multi-recipient To/Cc fields.

Working with Recipients and Attachments

Once a message is loaded, recipients and attachments are available as strongly-typed collections. Each MapiRecipient carries EmailAddress, DisplayName, and RecipientType fields. Each MapiAttachment provides Filename, Data, MimeType, and ContentId.

var message = MapiMessage.FromFile("report.msg");

foreach (var recipient in message.Recipients)
{
    Console.WriteLine($"  {recipient.DisplayName} <{recipient.EmailAddress}> (type={recipient.RecipientType})");
}

foreach (var attachment in message.Attachments)
{
    Console.WriteLine($"  {attachment.Filename} ({attachment.MimeType}, {attachment.Data.Length} bytes)");
    File.WriteAllBytes(attachment.Filename!, attachment.Data);
}

Embedded message attachments are also supported. When MapiAttachment.IsEmbeddedMessage is true, the EmbeddedMessage property returns a nested MapiMessage that you can inspect recursively.

Accessing MAPI Properties

For advanced scenarios, every message, recipient, and attachment exposes a Properties collection of type MapiPropertyCollection. You can read, add, and remove individual MAPI properties by their property ID and type code.

var message = MapiMessage.FromFile("custom-props.msg");

// Iterate all properties on the message
foreach (var prop in message.Properties.IterProperties())
{
    Console.WriteLine($"Tag: 0x{prop.PropertyTag:X8}, Value: {prop.Value}");
}

// Read a specific property
var subject = message.Properties.Get(
    (ushort)CommonMessagePropertyId.Subject,
    (ushort)PropertyTypeCode.PtypString);

The CommonMessagePropertyId enum defines well-known MAPI property identifiers for core message semantics, body fields, transport headers, and attachment metadata.

Converting Between MSG and EML

A parsed message can be saved back in either format. Save() writes MSG bytes, while SaveToEml() produces MIME output. This makes format conversion straightforward.

// Load an EML and save as MSG
var message = MapiMessage.LoadFromEml("incoming.eml");
message.Save("converted.msg");

// Load an MSG and save as EML
var msg = MapiMessage.FromFile("outgoing.msg");
msg.SaveToEml("exported.eml");

Both methods also accept a Stream parameter for in-memory processing without touching the file system.


Quick Start

Install the package from NuGet:

dotnet add package aspose.email.foss

Then parse your first message:

using Aspose.Email.Foss.Msg;

// Load an MSG file
var message = MapiMessage.FromFile("sample.msg");

Console.WriteLine($"Subject:     {message.Subject}");
Console.WriteLine($"From:        {message.SenderName} <{message.SenderEmailAddress}>");
Console.WriteLine($"Date:        {message.MessageDeliveryTime}");
Console.WriteLine($"Body length: {message.Body?.Length ?? 0} chars");

// List attachments
foreach (var att in message.Attachments)
    Console.WriteLine($"  Attachment: {att.Filename} ({att.Data.Length} bytes)");

// Convert to EML
message.SaveToEml("sample.eml");
Console.WriteLine("Saved as EML.");

Supported Formats

FormatExtensionReadWrite
Outlook MSG.msgYesYes
MIME (EML).emlYesYes
Compound File Binary.cfbYesYes

Open Source & Licensing

Aspose.Email FOSS for .NET is released under the MIT license. The full source code is available on GitHub at aspose-email-foss/Aspose.Email-FOSS-for-.Net. Contributions, issues, and pull requests are welcome.


Getting Started