Table of Contents

Properties and named properties

Everything a message carries is a MAPI property: a 32-bit tag - a 16-bit identifier plus a 16-bit wire type - paired with a value. The conveniences on OutlookMessage are curated views; the full surface is the tag-addressed MapiPropertyCollection on Properties, and the same collection shape hangs off every recipient and attachment.

The raw property surface

using Bodu.Formats.Outlook;

using var message = OutlookMessage.OpenRead("invoice.msg");

// Typed accessors probe the plausible wire types for an identifier and
// return null when absent - they never throw for a missing property.
string? subject = message.Properties.GetString(MapiPropertyIds.Subject);
int? codePage = message.Properties.GetInt32(MapiPropertyIds.MessageCodepage);
DateTimeOffset? sent = message.Properties.GetDateTime(MapiPropertyIds.ClientSubmitTime);

// Or address a property by its full tag and inspect the decoded value.
var tag = new MapiPropertyTag(MapiPropertyIds.Subject, MapiPropertyType.Unicode);
if (message.Properties.TryGetValue(tag, out MapiProperty? property))
    Console.WriteLine($"{property.Tag} = {property.Value}");

// Enumerate everything the message stores.
foreach (MapiProperty item in message.Properties)
    Console.WriteLine(item);

MapiPropertyIds carries the curated well-known identifiers (PidTag*); any property is reachable by raw identifier whether or not it is listed there.

Tags and wire types

MapiPropertyTag decomposes the 32-bit tag: Id, the base Type (with the multi-valued flag stripped), IsMultiValued, and IsNamed for identifiers at or above 0x8000. ToString renders the canonical hexadecimal form, for example 0x0037001F - the subject as a Unicode string.

Multi-valued properties decode to arrays (string[], int[], byte[][], …) and are addressed through the multi-valued accessors such as GetStringArray.

Named properties

Identifiers at or above 0x8000 are file-specific: their meaning comes from the message's named-property mapping, which pairs each identifier with a durable MapiNamedProperty - a property-set GUID plus a numeric identifier or a string name. Resolve in either direction:

using Bodu.Formats.Outlook;

using var message = OutlookMessage.OpenRead("invoice.msg");

// PS_PUBLIC_STRINGS "Keywords" - the category list.
var keywords = new MapiNamedProperty(
    new Guid("00020329-0000-0000-C000-000000000046"), "Keywords");

if (message.TryGetNamedPropertyId(keywords, out ushort id))
{
    string[]? categories = message.Properties.GetStringArray(id);
    Console.WriteLine(string.Join(", ", categories ?? Array.Empty<string>()));
}

// The reverse direction: what does a named identifier mean in this file?
if (message.TryGetPropertyName(new MapiPropertyTag(0x8000101F), out MapiNamedProperty name))
    Console.WriteLine(name);

The mapping is parsed lazily from the root's __nameid_version1.0 storage and, per MS-OXMSG, is shared by every nested attached message.