What RecursiveAI.OneMail.Mapi is: the in-memory form of an Outlook item (message, recipient, attachment), independent of
the file it came from. MSG files fill it today (docs/msg.md); the PST/OST messaging layer fills the same types (docs/pst-messaging.md). Types and tags are
MS-OXCDATA 2.11.1 and MS-OXPROPS; nothing here is taken from another implementation.
Objects
MapiMessage,MapiRecipient,MapiAttachmentderive fromMapiObject, which owns aMapiPropertyBag, a list ofMapiDefects and a link to its parent.- Recipients and attachments load on first use. An attachment that is an embedded message exposes it as
EmbeddedMessage(aMapiMessageof its own); other object attachments exposeObjectStorage, aMapiRawStoragethat keeps the storage's streams byte for byte so nothing is lost. MapiMessage.Subject,Body,MessageClass, recipient and attachment accessors are conveniences overProperties; the bag is the truth.
The property bag
- A
MapiPropertyis(tag, flags, value). The tag is a 16-bit ID plus a 16-bitMapiType; IDs 0x8000 and up are named properties and carry theirMapiNamedProperty(property-set GUID plus a number or a string). - The raw value is always kept: the bytes of the entry or value stream, read lazily through a
MapiValueSource. Typed accessors (AsInt32,AsDouble,AsBoolean,AsFileTime,AsGuid,AsString(encoding),CurrencyUnits...) decode on demand and never change what is stored. Round-tripping a property never goes through a typed value. - The bag keeps insertion order and duplicates. Lookups by ID (optionally restricted to types) return the last entry with
that tag (
AmbiguousDuplicateProperty).Versionincrements on change so callers can cache. - A string property is
String8(code page, seedocs/msg.md) orUnicodeString(UTF-16LE). Asking for a string by ID accepts either type.
Names
tools/mapi/properties.tsv lists the properties the readers use (271 tagged, 75 named), each with its canonical MS-OXPROPS
name. tools/mapi/gen_properties.py turns it into the generated MapiPid.g.cs constants and the MapiPropertyNames lookup.
The table is a static array: no reflection, so trimming and AOT keep it. Edit the TSV, never the generated file.
Defects
MapiDefect has a Kind, a message and a location (object path plus stream name). Kinds named Ambiguous* are documented
resolutions of known ambiguities (docs/ambiguities.md, section MAPI/MSG); the rest are repairs. The reader never throws on
damaged content. Defect lists are capped by MsgReaderOptions.MaxDefects; later ones are only counted.