Mailbox Canonical Model¶
This page defines the message format that the filesystem mailbox transport treats as authoritative content.
Mental Model¶
The mailbox system separates durable message content from per-recipient mailbox state.
- The canonical message is an immutable Markdown file with YAML front matter.
- The file captures who sent the message, who received it, how it threads, and what attachments it refers to.
- Read, answered, starred, archived, deleted, and thread summary state do not rewrite that canonical file. They live in
index.sqlite.
That split matters because repair can rebuild projections and mutable state around canonical messages, but it cannot invent missing canonical content.
Exact Contract¶
Protocol version and identity fields¶
protocol_versionmust be1.message_idandthread_idmust matchmsg-{YYYYMMDDTHHMMSSZ}-{uuid4-no-dashes}.created_at_utcmust be an RFC3339 UTC timestamp ending inZ.- The YAML front matter uses
fromas the serialized field name for the sender, even though the Python model field issender.
Addressing¶
Each participant is a MailboxPrincipal with:
principal_idaddress- optional
display_name - optional
manifest_path_hint - optional
role
Addresses are full-form email-like strings such as research@houmao.localhost. Whitespace, blank values, invalid domains, and unsafe literal path-segment values are rejected. Newly derived managed-agent addresses use <agentname>@houmao.localhost, while HOUMAO-* locals under houmao.localhost are reserved for Houmao-owned system mailboxes such as HOUMAO-operator@houmao.localhost.
Threading¶
- A root message uses its own
message_idasthread_id. - A root message must not include
references. - A reply keeps the existing
thread_id. - A reply must set
in_reply_to. - A reply must include
references, and the last element ofreferencesmust equalin_reply_to. - Subject changes do not create a new thread by themselves.
Recipients and attachments¶
tomust contain at least one recipient.ccandreply_toare optional lists.- Attachments are structured metadata, not free-form blobs embedded into the canonical message model.
- Attachment
kindis eitherpath_reformanaged_copy. path_refattachments must use absolute paths.- Optional
sha256values must be 64-character lowercase hex digests.
Headers and body¶
subjectmust be non-blank.body_markdownmay be empty, but it must not contain NUL bytes.headersis an extensible mapping, but header keys must be non-blank.- Operator-origin messages use explicit provenance headers such as
x-houmao-origin: operatorplusx-houmao-reply-policy: noneorx-houmao-reply-policy: operator_mailbox. - New operator-origin messages default to
x-houmao-reply-policy: operator_mailbox; theirreply_totargets the reserved system mailboxHOUMAO-operator@houmao.localhost. Withnone, replies to that operator-origin message are rejected.
Notification-prompt block¶
- Optional
notify_blockis a typed sub-modelMailboxNotifyBlockwith fieldstext: str(the prominent sender-authored content) andplacement: Literal["append", "prepend"](defaults toappend). Senders may author the text inline as a Markdown fenced code block with info-stringhoumao-notify; canonical-message construction extracts the first such fence intonotify_block.textwithplacement="append"and leavesbody_markdownunchanged. Callers may also supplynotify_blockdirectly through composition surfaces (MailboxMessage.compose(...), scopedhoumao-mgr agents single/self ... mail send --notify-block ... --notify-block-placement [append|prepend], gateway/v1/mail/sendrequest body); explicit values bypass body-fence extraction. notify_block.textis capped at 512 characters; longer values are truncated to 511 characters plus a single trailing…(U+2026) at composition time.- Auto-mirror invariant: when
notify_blockis supplied directly andbody_markdowndoes not already contain ahoumao-notifyfence, canonical-message construction synthesizes a fenced code block at the requestedplacementso the same text always appears verbatim somewhere inbody_markdown. The body fence is the source of truth;notify_blockis a typed convenience surface. This invariant letsnotify_blockwork as a priority surface — sender content reaches the receiver's wake-up prompt earlier — without becoming a covert channel invisible to ordinary mail readers (Stalwart JMAP projection, plain SMTP-bridged delivery, archival exports). - Optional
notify_authcarries sender-supplied authentication metadata associated withnotify_block. The protocol reserves the schemesnone,shared-token,hmac-sha256, andjws; in the current protocol version onlyscheme="none"is accepted at canonical-envelope validation. Non-noneschemes are rejected with an explicit "verifier not yet supported" error. The gateway notifier consumesnotify_auththrough a pluggable verifier interface (PermissiveVerifierfornone,SharedTokenVerifierforshared-token); both shipped implementations are described in Gateway Protocol And State Contracts. - The gateway notifier prompt renders
notify_block.textat the requestedplacement(prepend slot before the inbox opener, append slot after the mailbox API summary), with sender-attribution prefixSender notice — from <address>: > <text>, oldest-first ordering within each placement cluster, per-message and aggregate caps, and a+ N more sender notice(s) — open inbox to readsummary line when the aggregate cap fires. The trust posture (notify_block_render,notify_block_auth_mode,notify_block_auth_verifier) is configurable per gateway notifier; defaults areenabled/permissive-log/none.
Representative Canonical Message¶
This is the shape serialized by serialize_message_document() and parsed by parse_message_document().
---
protocol_version: 3
message_id: msg-20260313T091530Z-a1b2c3d4e5f64798aabbccddeeff0011
thread_id: msg-20260313T091530Z-a1b2c3d4e5f64798aabbccddeeff0011
in_reply_to: null
references: []
created_at_utc: 2026-03-13T09:15:30Z
from:
principal_id: HOUMAO-research
address: research@houmao.localhost
to:
- principal_id: HOUMAO-orchestrator
address: orchestrator@houmao.localhost
cc: []
reply_to: []
subject: Investigate parser drift
attachments:
- attachment_id: att-1
kind: path_ref
path: /abs/path/notes.txt
media_type: text/plain
headers:
tags:
- parser
notify_block:
text: re-run on official timing path before reporting
placement: append
notify_auth:
scheme: none
---
# Summary
The parser drift appears after the second transform stage.
```houmao-notify
re-run on official timing path before reporting
Immutable Versus Mutable Boundaries¶
Authoritative immutable content:
messages/<YYYY-MM-DD>/<message-id>.md- front matter values such as sender, recipients, thread metadata, and attachments
- Markdown body text
Authoritative mutable state:
mailbox_staterows for read, starred, archived, and deleted flagsthread_summariesrows for normalized subject, latest message, and unread countsmailbox_projectionsrows and the corresponding symlinks ininbox/andsent/
Practical rule: if you are changing recipient-visible status, you should be touching SQLite-backed state, not rewriting a delivered Markdown file.