Table of Contents

Bodu.Formats.Outlook Namespace

Bodu.Formats.Outlook

Purpose

Bodu.Formats.Outlook is the single, flattened namespace shared by three packages: the container-free MAPI value model (Bodu.Formats.Outlook) and the two read-only readers built on it - Bodu.Formats.Outlook.Msg, which opens one message (.msg / MS-OXMSG) over the Bodu.IO.Compound OLE2 container, and Bodu.Formats.Outlook.Pst, which opens a personal-folders mail store (.pst / MS-PST, Unicode and ANSI formats) over the Bodu.IO.Pst node database. The package names keep their suffixes; the public types do not - a .msg recipient and a .pst recipient are the same OutlookRecipient over the same MapiPropertyCollection, and code written against one reader's property surface carries over to the other. The container-specific decoders stay internal under Bodu.Formats.Outlook.Msg and Bodu.Formats.Outlook.Pst.

Both readers open a disposable session that owns the container, decode every MAPI property into the shared model, and layer curated conveniences (subject, sender, time stamps, recipients, attachments, the text / HTML / compressed-RTF bodies, named-property resolution) over the raw tag-addressed surface. Neither writes, emulates a MAPI session, or de-encapsulates RTF.

Static documentation

Key types

Value model - Bodu.Formats.Outlook

  • MapiPropertyTag - the 32-bit tag (MS-OXCDATA §2.11.4): Id in the high word, base Type in the low word with the 0x1000 multi-valued flag stripped, IsMultiValued, and IsNamed (identifier ≥ 0x8000); constructed from (id, type) or a raw uint, or via ForMultiValue(id, elementType).
  • MapiPropertyType - the 16-bit wire-type codes: Int16 / Int32 / Int64, Float / Double, Currency, AppTime, ErrorCode, Boolean, Object, String8 (code-paged) / Unicode, SystemTime, Guid, Binary, plus Unspecified / Null.
  • MapiProperty - one decoded property: Tag and a CLR Value (arrays for multi-valued properties).
  • MapiPropertyCollection - the tag-addressed, first-occurrence-ordered bag on every object. TryGetValue by tag or by identifier, GetAll(id), Contains(tag), Count, the shared Empty, and the typed accessors that probe the plausible wire types and return null when absent: GetString, GetInt32, GetInt64, GetBoolean, GetDouble, GetDateTime, GetGuid, GetBinary, GetStringArray.
  • MapiNamedProperty - the durable identity behind a named identifier: a property-set GUID with either a numeric Id or a string Name.
  • MapiPropertyIds - the curated PidTag* identifiers the conveniences use (Subject, SenderName, ClientSubmitTime, Body, Html, RtfCompressed, AttachMethod, AttachLongFilename, MessageCodepage, ContainerClass, …); any property is reachable by raw identifier regardless.
  • OutlookRecipient - one recipient: RecipientType, DisplayName, EmailAddress, AddressType, and its Properties. OutlookRecipientType - Originator / To / Cc / Bcc.
  • OutlookAttachmentMethod - None / ByValue / ByReference / ByReferenceResolve / ByReferenceOnly / EmbeddedMessage / Ole.

Message session - Bodu.Formats.Outlook.Msg

  • OutlookMessage - the disposable .msg session. Factories OpenRead (path / Stream) and Open(Stream, options, leaveOpen), the cheap IsMsgFile probe; Properties; the conveniences Subject (prefix marker stripped), SenderName, SenderEmailAddress, MessageClass, InternetMessageId, TransportMessageHeaders, SentTime, ReceivedTime; Recipients and Attachments (lazy, storage-index order); BodyText / BodyHtml / BodyRtf; TryGetNamedPropertyId / TryGetPropertyName over the __nameid_version1.0 mapping shared with nested messages; EmbeddedDepth.
  • OutlookAttachment - one attachment: Method (inferred as ByValue when a payload exists but the property is absent), FileName, ContentId, MimeTag, Size; OpenContentStream() for a by-value payload, OpenMessage() for an embedded message (a nested session sharing the root's container - disposing it is a no-op).

Mail-store session - Bodu.Formats.Outlook.Pst

  • OutlookMailStore - the disposable .pst session. Factories OpenRead (path / Stream) and Open(Stream, options, leaveOpen), the IsPstFile probe; Properties, DisplayName, RootFolder; the store-wide TryGetNamedPropertyId / TryGetPropertyName over the name-to-id map node. Single-threaded; every view is bound to the session's lifetime.
  • OutlookMailFolder - one folder: DisplayName, ContainerClass, the declared MessageCount / UnreadCount, HasSubfolders; the streaming EnumerateSubfolders (search folders excluded), EnumerateMessages, and EnumerateAssociatedMessages.
  • OutlookMailMessage - one message: the same convenience surface as OutlookMessage (Subject, SenderName, MessageClass, InternetMessageId, TransportMessageHeaders, the time stamps), Recipients (recipient-table order), Attachments, the three bodies, EmbeddedDepth.
  • OutlookMailAttachment - one attachment with the same surface as OutlookAttachment; OpenContentStream() streams a deferred payload block by block, OpenMessage() returns the embedded OutlookMailMessage with code-page inheritance.

Options

Errors

Example

using Bodu.Formats.Outlook;

// Value model: address a property by tag, or by identifier through a typed accessor.
var subjectTag = new MapiPropertyTag(MapiPropertyIds.Subject, MapiPropertyType.Unicode);
Console.WriteLine($"{subjectTag} named={subjectTag.IsNamed} multi={subjectTag.IsMultiValued}");   // 0x0037001F named=False multi=False
using Bodu.Formats.Outlook;

// .msg: open, read the conveniences, save the by-value attachments.
using var message = OutlookMessage.OpenRead("invoice.msg");
Console.WriteLine($"{message.Subject} - {message.SenderName} ({message.SentTime:u})");

foreach (OutlookAttachment attachment in message.Attachments)
{
    if (attachment.Method != OutlookAttachmentMethod.ByValue)
        continue;

    using Stream content = attachment.OpenContentStream();
    using FileStream target = File.Create(attachment.FileName ?? "attachment.bin");
    content.CopyTo(target);
}
using Bodu.Formats.Outlook;

// .pst: walk the folder hierarchy and resolve a named property store-wide.
using var store = OutlookMailStore.OpenRead("archive.pst");

var keywords = new MapiNamedProperty(new Guid("00020329-0000-0000-C000-000000000046"), "Keywords");
store.TryGetNamedPropertyId(keywords, out ushort keywordsId);

foreach (OutlookMailFolder folder in store.RootFolder.EnumerateSubfolders())
{
    foreach (OutlookMailMessage item in folder.EnumerateMessages())
    {
        string[] categories = item.Properties.GetStringArray(keywordsId) ?? Array.Empty<string>();
        Console.WriteLine($"{folder.DisplayName}: {item.Subject} [{string.Join(", ", categories)}]");
    }
}
using Bodu.Formats.Outlook;
using Bodu.IO.Pst;

// Options and errors: strict validation, tighter limits, and the two failure surfaces.
var options = new OutlookMailStoreReaderOptions
{
    ValidationLevel = PstValidationLevel.Strict,
    MaxEmbeddedMessageDepth = 4,
    MaxInlineAttachmentBytes = 256 * 1024,
};

try
{
    using var store = OutlookMailStore.Open(File.OpenRead("suspect.pst"), options);
    Console.WriteLine(store.DisplayName);
}
catch (PstFileException ex)          { Console.WriteLine($"container: {ex.Error}"); }
catch (OutlookPstFormatException ex) { Console.WriteLine($"messaging: {ex.Message}"); }

Notes

  • One namespace, three packages. Install Bodu.Formats.Outlook.Msg or Bodu.Formats.Outlook.Pst for a format; both reference Bodu.Formats.Outlook transitively. Reference the model package alone from a library that only handles properties.
  • Conveniences never throw for absence. Subject, SenderName, the time stamps, FileName, and the rest return null when the property is missing; the typed Get* accessors do the same. Only structural violations and tripped limits raise the format exceptions.
  • Code pages. String8 properties decode through the object's message code page, then its internet code page, then Windows-1252, with child objects inheriting; the readers register CodePagesEncodingProvider so Windows code pages resolve on every platform.
  • Compressed RTF is bounded. BodyRtf decompresses PidTagRtfCompressed (MS-OXRTFCP) on first access; the declared size lies outside the payload's checksum, so MaxDecompressedRtfBytes is enforced before allocation at every validation level. Set DecompressRtf = false to keep the raw payload only.
  • Large attachments stream. A by-value payload above MaxInlineAttachmentBytes is left in the container and served only through OpenContentStream() - in the .msg reader sector by sector under CompoundReadStrategy.Streaming, in the .pst reader block by block always.
  • Two error styles. The .msg reader wraps container failures in OutlookMsgFormatException; the .pst reader lets PstFileException propagate and reserves OutlookPstFormatException for messaging-level violations. Catch OutlookFormatException for the family and the container family beneath it.
  • See also: the introduction, core concepts, and getting started; the Bodu.Formats.Outlook guides; and the containers Bodu.IO.Compound and Bodu.IO.Pst.

Classes

MapiProperty

Represents a single decoded MAPI property: a MapiPropertyTag paired with its CLR value.

MapiPropertyCollection

Provides a read-only, tag-addressed collection of decoded MAPI properties with typed convenience accessors.

MapiPropertyIds

Provides curated well-known MAPI property identifiers (the PidTag* constants of MS-OXPROPS).

OutlookAttachment

Represents one attachment of an OutlookMessage: a typed view over the attachment storage's decoded properties, with access to the by-value payload or the nested attached message.

OutlookFormatException

Represents an error raised when Outlook-format content is structurally invalid or cannot be decoded.

OutlookMailAttachment

Represents one attachment of an OutlookMailMessage: a typed view over the attachment object's decoded properties, with access to the by-value payload or the nested attached message.

OutlookMailFolder

Represents one folder of a mail store: its decoded properties and streaming access to its subfolders and messages.

OutlookMailMessage

Represents one message of a mail store: its decoded properties with typed conveniences over the well-known scalars.

OutlookMailStore

Provides a disposable, read-only session over an Outlook personal-folders mail store (a .pst file, MS-PST Unicode or ANSI format): the store properties, the folder hierarchy, and the messages within it, decoded into the shared MAPI value model.

OutlookMailStoreReaderOptions

Controls how an OutlookMailStore reads its PST container and decodes message content.

OutlookMessage

Provides a disposable, read-only session over an Outlook message (.msg / MS-OXMSG), exposing every decoded MAPI property together with curated conveniences for the common message fields.

OutlookMessageReaderOptions

Controls how an OutlookMessage opens its container and handles malformed content.

OutlookMsgFormatException

Represents an error raised when a .msg file is structurally invalid or violates the MS-OXMSG format.

OutlookPstFormatException

Represents a violation of the Outlook personal-folders messaging conventions (MS-PST) while reading a mail store.

OutlookRecipient

Represents one recipient of a message: a typed view over the recipient's decoded properties.

Structs

MapiNamedProperty

Represents the identity of a MAPI named property: a property-set GUID combined with either a numeric identifier or a string name.

MapiPropertyTag

Represents a 32-bit MAPI property tag: a 16-bit property identifier in the high word and a 16-bit property type in the low word.

Enums

MapiPropertyType

Specifies the 16-bit MAPI property type codes (the PT_* constants) carried in the low word of a MapiPropertyTag.

OutlookAttachmentMethod

Specifies how an attachment's content is stored or referenced (the PidTagAttachMethod values).

OutlookRecipientType

Specifies how a recipient participates in a message (the PidTagRecipientType values).