X-Ray.Email
26.10.7077
dotnet add package X-Ray.Email --version 26.10.7077
NuGet\Install-Package X-Ray.Email -Version 26.10.7077
<PackageReference Include="X-Ray.Email" Version="26.10.7077" />
<PackageVersion Include="X-Ray.Email" Version="26.10.7077" />
<PackageReference Include="X-Ray.Email" />
paket add X-Ray.Email --version 26.10.7077
#r "nuget: X-Ray.Email, 26.10.7077"
#:package X-Ray.Email@26.10.7077
#addin nuget:?package=X-Ray.Email&version=26.10.7077
#tool nuget:?package=X-Ray.Email&version=26.10.7077

X-Ray.Email
A .NET 10 library for Outlook .msg files, Compound File Binary (CFB) containers and EML/MIME
messages: read them, create and edit them, and convert between MSG and EML. It has no
dependencies outside the .NET base class library and needs no Outlook installation.
Part of the X-Ray family of document libraries.
Install
dotnet add package X-Ray.Email
Features
- MSG: read and write Outlook messages at two levels.
MapiMessageexposes subject, bodies (plain text, HTML, and RTF decompressed from PR_RTF_COMPRESSED), sender, recipients, attachments, embedded messages and arbitrary MAPI properties.MsgReader,MsgWriterandMsgDocumentexpose the raw property streams and storages. - EML: a MIME reader and writer built in. It handles multipart structures, nested
message/rfc822parts, quoted-printable and base64, RFC 2047 encoded words in any charset the platform provides, raw UTF-8 headers (RFC 6532), RFC 2231 parameters, address groups and comments, and obsolete date syntax. Damaged input degrades instead of failing: missing closing boundaries, truncated base64, broken header folding. - Conversion:
MapiMessage.LoadFromEmlturns an EML into a message that saves as MSG;SaveToEmlwrites any message as MIME, with non-ASCII headers and file names encoded. - Exchange addresses:
SenderSmtpAddressandMapiRecipient.SmtpAddressresolve the SMTP address of senders and recipients that Outlook stores as X.500 names. - HTML preview:
message.ToHtml()renders one self-contained, deterministic HTML document in the layout Outlook displays: a header block (From, Sent, To, Cc, Bcc, Subject, Attachments) above the body. Scripts, event handlers and scripting URLs are removed by default,cid:images are inlined asdata:URIs, and the output is UTF-8 with the charset declared. - Body fallbacks:
GetBestTextBody()andGetBestHtmlBody()give a body whichever one the message stores: HTML, else RTF (the original HTML is recovered from encapsulated-HTML RTF, [MS-OXRTFEX]; other RTF is rendered), else plain text. - Attachment adapters:
GetAttachmentsFlattened()lists attachments as files: safe unique names, content type, size, content id, inline flag,OpenRead()andCopyTo/CopyToAsync. Embedded messages are named from their subject and serialised on demand as MSG or EML, and can be flattened to any depth. - Streams and cancellation: loading reads an MSG from a file or seekable stream on demand
instead of copying it, and saving writes to a
Streamsector by sector (MSG) or part by part (EML) instead of building the whole file. Every load, save, convert, render and copy method takes aCancellationTokenand stops promptly. - CFB:
CfbReaderandCfbWriterread and write compound files (versions 3 and 4) independently of MSG. - Lenient by default: anomalies in a damaged MSG (trailing bytes in a property stream,
counts that do not match) are reported in
ValidationIssues. Passstrict: trueto reject them instead. Malformed input raisesCfbExceptionorMsgException.
Quick start
Read a message, whichever format it is in:
using XRay.Email.Msg;
var message = path.EndsWith(".eml", StringComparison.OrdinalIgnoreCase)
? MapiMessage.LoadFromEml(path)
: MapiMessage.FromFile(path);
Console.WriteLine(message.Subject);
Console.WriteLine($"{message.SenderName} <{message.SenderSmtpAddress}>");
foreach (var recipient in message.Recipients)
{
Console.WriteLine($" to: {recipient.DisplayName} <{recipient.SmtpAddress ?? recipient.EmailAddress}>");
}
Console.WriteLine(message.Body ?? message.HtmlBody ?? message.RtfBody);
foreach (var attachment in message.Attachments)
{
Console.WriteLine(attachment.IsEmbeddedMessage
? $" message: {attachment.EmbeddedMessage?.Subject}"
: $" file: {attachment.Filename} ({attachment.Data.Length} bytes)");
}
Create a message and save it in both formats:
using XRay.Email.Msg;
var message = MapiMessage.Create("Quarterly report", "Please find the report attached.");
message.SenderName = "Alice";
message.SenderEmailAddress = "alice@example.com";
message.AddRecipient("bob@example.com", "Bob");
message.AddRecipient("carol@example.com", "Carol", MapiMessage.RecipientTypeCc);
message.AddAttachment("report.pdf", File.ReadAllBytes("report.pdf"), "application/pdf");
message.Save("report.msg");
message.SaveToEml("report.eml");
Preview a message as HTML, read the best body, and store the attachments as files:
using XRay.Email.Msg;
using var input = File.OpenRead(path);
var message = MapiMessage.Load(input, cancellationToken); // MSG or EML, detected from the bytes
await using (var html = File.Create("preview.html"))
{
message.WriteHtml(html, new MailHtmlOptions { LinkifyPlainText = true }, cancellationToken);
}
string? text = message.GetBestTextBody(cancellationToken); // plain text, derived if need be
bool unread = !message.IsRead; // PR_MESSAGE_FLAGS
DateTime? sent = message.SentTime; // UTC
foreach (var attachment in message.GetAttachmentsFlattened(new AttachmentFlattenOptions { MaxDepth = 2 }, cancellationToken))
{
await using var file = File.Create(Path.Combine(folder, attachment.Name));
await attachment.CopyToAsync(file, cancellationToken); // an embedded message is written as .msg or .eml
}
The HTML preview
ToHtml(MailHtmlOptions?, CancellationToken) and WriteHtml(Stream, ...) pick the body in this
order: the HTML body; the RTF body (encapsulated HTML comes back as the original HTML, plain RTF
is rendered with its character formatting, colours, fields, tables and PNG/JPEG pictures); the
plain text body, escaped and with its whitespace preserved. An RTF that only wraps plain text
defers to the plain text body.
| Option | Default | Effect |
|---|---|---|
SanitizeHtml |
on | removes script, iframe, object, embed, applet, base, meta refresh, event handler attributes, javascript:/vbscript:/data: (other than images) URLs, CSS expression(), behavior, @import |
EmbedInlineImages |
on | cid: references (and an attachment's Content-Location) become data: URIs |
MaxInlineImageBytes |
10 MiB | larger images keep their cid: reference |
IncludeHeader |
on | the From/Sent/To/Cc/Bcc/Subject/Attachments block |
ListInlineAttachments |
off | list attachments the body already shows |
LinkifyPlainText |
off | link http, https and www addresses in a plain text body |
FullDocument |
on | doctype, head and body; off gives a fragment |
DateFormat, TimeZone, ShowTimeZone, Culture |
Outlook format, UTC, on, invariant | the Sent line |
The output has no timestamps and does not depend on the machine, so it can be compared in tests. Sanitising is a filter for display, not a security boundary for untrusted mail: serve the page from an origin that holds nothing, or in a sandboxed frame.
Streams
Existing stream-capable members: MapiMessage.FromStream, LoadFromEml(Stream), Save(Stream),
SaveToEml(Stream), AddAttachment(string, Stream), MsgDocument.FromStream, CfbReader.FromStream.
Now:
FromFileand a seekableFromStreamread sectors on demand; a non-seekable stream is spooled to a temporary file (deleted on dispose), never held whole in memory. The parsed message still holds its properties and attachment bytes.Save(Stream),SaveToEml(Stream),MsgWriter.WriteandCfbWriter.Writewrite straight to the stream: an MSG a sector at a time, an EML with attachments base64-encoded block by block. Thebyte[]forms are thin wrappers.- EML is parsed from one block (decoded parts stay in memory anyway), so
LoadFromEmlreads the stream into one exactly sized array. MapiMessage.Load(Stream|path)detects MSG or EML.
Convert an EML file to MSG:
MapiMessage.LoadFromEml("message.eml").Save("message.msg");
Walk the CFB structure of any compound file:
using XRay.Email.Cfb;
using var cfb = CfbReader.FromFile("message.msg");
foreach (var (depth, entry) in cfb.IterTree())
{
Console.WriteLine($"{new string(' ', depth * 2)}{entry.Name} ({entry.StreamSize} bytes)");
}
Scope
X-Ray.Email works on files and streams. It does not talk to mail servers (no IMAP, POP3 or
SMTP), does not read PST/OST mailboxes, and has no dedicated calendar or contact API (those MAPI
properties are reachable through GetPropertyValue). S/MIME (signed and encrypted messages,
smime.p7m) and TNEF (winmail.dat) are not supported: such a message shows whatever body
it carries and the opaque part as an attachment. RtfBody returns the RTF itself;
GetBestHtmlBody and ToHtml are the converted forms.
License
MIT. X-Ray.Email is derived from an MIT-licensed open-source C# MSG/EML toolkit; its licence
and copyright notice are in LICENSE, which ships in the package.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on X-Ray.Email:
| Package | Downloads |
|---|---|
|
X-Ray
The umbrella package for the X-Ray family of .NET libraries. Carries no code of its own: installing it brings in every X-Ray package, for content extraction (X-Ray.Content), rendering office documents to PDF and images (X-Ray.Office), and reading Photoshop (X-Ray.Psd), email (X-Ray.Email), iCalendar (X-Ray.Ics), PostScript and XPS (X-Ray.PS) and OneNote (X-Ray.OneNote) files. Install a single member directly if you only need one. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 26.10.7077 | 41 | 10/21/2026 |
| 26.10.7057 | 59 | 10/20/2026 |
| 26.10.7019 | 75 | 10/19/2026 |
| 26.10.6966 | 86 | 10/19/2026 |
| 26.10.6963 | 73 | 10/19/2026 |