# Agent Mail Protocol 1.0 [English](1.0.md) | [简体中文](1.0.zh-CN.md) **Identifier**: `agent-mail/1.0` **Status**: Draft **Date**: 2026-10-01 **Depends on**: Agent Identity Protocol 1.0 **Core objects**: Mailbox Card, Message, Packet ## 1. Overview Agent Mail provides asynchronous, end-to-end encrypted private correspondence between agents. An Agent ID is the correspondent's identity; a mailbox is a replaceable delivery location. Agents can operate their own mailboxes, use independent providers, or publish several routes for the same mailbox. No global directory, home server, blockchain, shared room, or relay-to-relay federation is required. The recipient signs a **Mailbox Card** binding its identity to a separately generated encryption key and delivery routes. The sender encrypts an immutable **Message** and signs the ciphertext as a **Packet**, a `mail.submit` envelope. Relays verify the sender's signature and can apply admission policy before storing the packet. The recipient verifies the packet and its own sender policy before decrypting locally. A sender needs a verified card, but neither an account at the recipient's relay nor a simultaneous connection to the recipient. The email analogy covers durable correspondence, subjects, replies, attachments, and offline delivery. It does not import SMTP, DNS-based identities, or the assumption that a mail provider may read messages. A private letter can carry a question, proposal, research artifact, or structured request; receiving it never grants permission to execute it. ```mermaid sequenceDiagram participant A as Sending agent participant R as Recipient-chosen relay(s) participant B as Receiving agent B->>R: Publish identity-signed card and public encryption key A->>R: Fetch card (or obtain it privately) R-->>A: Recipient-signed card Note over A: Verify recipient; encrypt message; sign packet A->>R: Sender-signed encrypted packet R-->>A: Signature and admission checked; ciphertext accepted Note over R: Store while recipient is offline B->>R: Poll with owner authentication R-->>B: Signed encrypted packets Note over B: Verify sender and policy; decrypt; deduplicate; persist B->>R: Delete locally persisted packet Note over A,B: An acknowledgement is an ordinary encrypted reply ``` Version 1.0 defines one-recipient messages, one encryption suite, bounded inline attachments, mailbox rotation, and retries. Group encryption, multi-device key distribution, large external attachments, receipts, ratchets, post-quantum encryption, anonymous routing, and guaranteed delivery are outside this version. This is a specification draft. Rust, TypeScript, and Python SDKs provide protocol building blocks; applications supply durable storage and operating policy. The [local MCP connector](../../mcp/local-connector/1.0.md) adapts the sender and recipient roles as agent tools. ## 2. Normative Language The capitalized keywords `MUST`, `MUST NOT`, `SHOULD`, `SHOULD NOT`, and `MAY` have the meanings in BCP 14 (RFC 2119 and RFC 8174). [Agent Identity](../agent-identity/1.0.md) defines Agent IDs, strict I-JSON, JCS, SHA3-256 event hashes, deterministic Ed25519 verification, canonical unpadded base64url, millisecond timestamps, origins, and HTTP conventions. Those rules apply here. Every object defined below is closed unless explicitly stated otherwise. Unknown fields and event types MUST be rejected. Shared Identity errors, lists, and discovery retain Identity's extension rules. All integers MUST be safe JSON integers. Missing and `null` are different; no field defined here accepts `null`. `B64(x)` encodes bytes as canonical unpadded base64url; `H(x)` is SHA3-256; `UTF8(s)` encodes a string; `||` concatenates bytes. An `id16` is 16 cryptographically random bytes encoded with `B64`; hashes and X25519 public keys encode 32 bytes. Random IDs MUST NOT be derived from an Agent ID or a human-readable name. All time comparisons use Unix milliseconds. `DAY = 86400000`; `MAX_TTL = 30 * DAY`; permitted future clock skew is `300000` ms. ## 3. Identity, Addressing, and Discovery ### 3.1 Identity and Mailboxes The intended recipient is a full `did:agent:` Agent ID. A mailbox is identified by `(owner Agent ID, mailbox_id)`. One owner MAY have several mailboxes, including a public introduction mailbox and privately shared mailboxes for existing correspondents. The same mailbox MAY be served at several independent HTTPS origins. Knowing a mailbox identifier permits attempting delivery, not reading its contents. Random identifiers reduce enumeration; they are not an authorization system or a substitute for encryption. A relay MUST bind a mailbox identifier to its first accepted card's `actor` and MUST NOT let another actor update or claim that identifier while it knows the mailbox. The sender MUST determine the intended Agent ID from its application or an authenticated introduction before trusting a card. Taking both an unfamiliar identity and its key from the same unauthenticated directory does not authenticate the intended correspondent. Familiar names and email-style aliases are application hints, not protocol identities. ### 3.2 Obtaining a Card A card MAY be shared directly, embedded in an encrypted message, fetched from a contact address (Section 3.3), or fetched through a verified Agent Profile service endpoint: ```json { "type": "agent-mail", "url": "https://relay.example/v1/mailboxes/AAECAwQFBgcICQoLDA0ODw/card", "protocols": ["agent-mail/1.0"] } ``` Here `url` is the card-read URL of Section 8.2 at one route. No Profile schema change is required. Profile is OPTIONAL and its endpoint is only a discovery hint. A fetched card MUST still be verified under the expected recipient's Agent ID. Clients MUST NOT substitute an unsigned encryption key or fall back to plaintext when discovery or verification fails. Before sending, a client MUST check the card's signature, owner, shape, time bounds, key, and routes. It MUST durably **pin** the valid card with the greatest nonce it has observed for each `(owner, mailbox_id)`, including a card that is closed or expired. A card is accepted only if its nonce is greater than the pinned nonce or its hash equals the pinned hash; any other card, including a different card with the same nonce, is rejected and the pin is kept. A higher-nonce closed or expired card therefore never lets an older card be used again. The pin MAY be discarded once `now >= pin.created_at + MAX_TTL + 300000`, when every earlier card has expired. A fetched card is a historical signed object, so fetching it does not apply Identity's live-write window or nonce cache. A signed card proves origin, not global freshness. Clients SHOULD refresh cards before sending; a first-time sender cannot detect a withheld newer card without another trusted source, and card expiry limits this exposure. ### 3.3 Mailbox Addresses A mailbox's **stable address** is the Agent URL (Identity Section 3.5) `did:agent:/mail/`, where `` is the owner's public key and `mailbox_id` is the mailbox's `id16`. It is the sender's pin key `(owner, mailbox_id)` written as one string. A **contact address** adds one to eight `route` parameters naming card routes: ```text did:agent:6kpsY-KcUgq-9VB7Ey7F-ZVHdq6-vnuSQh7qaRRG0iw/mail/AAECAwQFBgcICQoLDA0ODw?route=https://relay.example&route=https://mirror.example ``` A sender fetches `{route}/v1/mailboxes/{mailbox_id}/card` from any listed route, verifies the card under the owner's Agent ID, pins it as Section 3.2 requires, and thereafter uses the card's `routes`, discarding the address's. An address grants nothing and proves nothing: its Agent ID is the intended correspondent only when the address came from a source Section 3.1 accepts, the mailbox exists only if a verified card says so, and a route that serves no such card is simply unusable. A Profile endpoint's card URL and a contact address carry the same information because relay paths are fixed. A message's `to` remains a bare Agent ID, never an address. ## 4. Mailbox Cards and Key Lifecycle ### 4.1 Signed Card A card is an Agent Identity envelope whose event has exactly the six Identity fields, with `protocol: "agent-mail/1.0"`, `type: "mailbox.publish"`, and `actor` equal to the owner. Its payload is a full replacement: | Field | Required | Meaning | | --- | --- | --- | | `mailbox_id` | Yes | `id16`, stable across this mailbox's updates and routes. | | `expires_at` | Yes | New encryption under this card requires `now < expires_at`. | | `receive_until` | Yes | Upper bound on expiration of messages encrypted using this card. | | `public_key` | Yes | Raw X25519 public key, `B64` encoded. | | `routes` | Yes | Zero to eight unique canonical HTTPS service origins. An empty list closes the mailbox. | | `max_packet_bytes` | Yes | Maximum UTF-8 `JCS(packet)` size, integer from 4096 through 1048576. | The card MUST satisfy `created_at < expires_at <= created_at + MAX_TTL` and `expires_at <= receive_until <= expires_at + MAX_TTL`. A sender MUST use it only when `created_at <= now + 300000`, `now < expires_at`, and `routes` is non-empty, and MUST deliver only to origins in `routes`. A sender MUST NOT encrypt a message whose expiration exceeds `receive_until` under this card. Owners sign successive cards of one mailbox with increasing nonces and non-decreasing `created_at`. Encryption keys MUST be generated independently of Ed25519 signing keys. Implementations MUST NOT convert or reuse the identity secret as the X25519 secret. Rotating a key publishes a card with a new `public_key`; an unchanged key MAY appear in several cards, for example when only routes change. X25519 key processing and all-zero shared-secret rejection MUST follow RFC 9180 Section 7.1. Senders MUST reject unusable keys rather than retry with weaker cryptography. `card_hash` means the Identity event hash of this signed card. It commits to the owner, key, routes, limits, times, and nonce without placing the full card in every packet, and it is the only key selector a packet carries. ### 4.2 Publishing and Changing Routes Publishing a card to a relay is a **live control write** and MUST pass all Identity live-write rules, including timestamp, service-wide actor nonce checks, and exact resubmission. The first card a relay accepts for a mailbox MUST list the relay's origin in `routes`; later updates need not. Each update's nonce MUST also exceed the nonce of the relay's current card for the mailbox. The binding and current card MUST survive replay-cache expiration. A relay MAY forget a mailbox and its binding once the current card's `receive_until` has passed; every packet and tombstone of that mailbox has expired by then, and a later card for the same identifier is a new registration. A service MAY decline new mailbox registration according to operator policy. An update atomically replaces the current card. A relay keeps only its current card: an exact resubmission of that card returns its original accepted record, and any older card is answered as a stale write (`nonce_not_greater`), as Identity Section 6.3 permits for pruned history. The relay accepts new packets only while its origin is in the current card's `routes` and the card is unexpired. Already accepted packets remain retrievable until their expiration, even after rotation or route removal. To change routes, the owner publishes one higher-nonce card to every old and new route within the live-write window. If a route cannot be updated before the window closes, the owner signs another higher-nonce card and publishes it to every route again; a relay left with an older card rejects packets encrypted under the newer card as `stale_card`. A relay absent from the new `routes` stops accepting packets and serves that card, whose routes tell senders where to deliver instead. Publishing a card with `routes: []` closes the mailbox wherever it is published. A malicious or unavailable relay cannot be forced to update; its last card eventually expires. ### 4.3 Rotation, Migration, and Recovery The owner SHOULD rotate encryption keys regularly and MUST retain each old secret and every verified card that advertised it until that card's `receive_until`, unless it deliberately accepts loss of pending mail or is responding to compromise. Old keys MUST NOT be erased merely because the current card changed. After their retention period, old secrets SHOULD be erased from devices, backups, and custodians according to the owner's retention policy. For migration, the owner publishes a card covering the desired routes and distributes the new card or card URL. A new relay requires a freshly signed card within the live-write window. Relays do not need to synchronize or trust one another. The recipient polls old routes until their outstanding packets have been retrieved or expired. The sender MAY re-encrypt the same message under a newer verified card, signing a fresh packet if its recipient, expiration, and size remain acceptable. Compromise of an encryption key requires rotation and closing affected routes as appropriate. Compromise of the Ed25519 identity key permits forged cards and packets; recovering the same identity securely cannot be achieved by this protocol. Moving to another Agent ID requires an independently trusted identity transition. Multiple devices MAY use securely provisioned owner keys, but device enrollment, synchronization, and delegated mailbox access are not defined here. ## 5. Messages ### 5.1 Immutable Message A Message (`MailMessagePayload`) is a closed JSON object, encrypted in its entirety. It has no inner signature. Its authenticated sender comes from the packet signature and the HPKE context binding in Section 6. | Field | Required | Meaning | | --- | --- | --- | | `message_id` | Yes | `id16`; the stable logical message ID. | | `from` | Yes | Sender Agent ID; MUST equal the outer event's `actor`. | | `created_at` | Yes | Original message creation time, Unix milliseconds. | | `to` | Yes | Exactly one recipient Agent ID. | | `expires_at` | Yes | New local acceptance deadline. | | `thread_id` | Yes | `id16`; replies preserve the initiator's value. | | `parts` | Yes | One to 32 ordered content parts. | | `subject` | No | String of at most 1024 Unicode scalar values. | | `in_reply_to` | No | Parent's `message_id`; its author is this message's `to`. | | `reply_card` | No | Complete owner-signed Mailbox Card whose actor equals `from`. | Senders MUST generate a fresh `message_id` for each new logical message and MUST retain the exact original message for retries. Re-encryption, route changes and fresh packet signatures MUST NOT change any message field. Editing content, changing the recipient, or extending expiration creates a new message and MUST use a new ID. Messages MUST satisfy `created_at < expires_at <= created_at + MAX_TTL`. A message's creation time MUST NOT exceed either the recipient's current time or its packet's creation time by more than 300000 ms. Each part has REQUIRED `media_type` and `data`, and OPTIONAL `name`. `media_type` is a lowercase ASCII type/subtype with no parameters, matching `[a-z0-9][a-z0-9!#$&^_.+-]*/[a-z0-9][a-z0-9!#$&^_.+-]*`, at most 127 characters. `data` is canonical base64url of the content bytes and MAY be empty. A `text/*` part MUST decode to valid UTF-8. `name`, when present, contains one to 255 Unicode scalar values and is an inert display filename, never an instruction to write a file. Clients MUST support `text/plain`. Unsupported parts MUST remain opaque or be reported as unsupported and MUST NOT be executed. HTML, markdown, filenames and active documents require safe handling. External URLs MUST NOT be fetched automatically because a message contains them. ### 5.2 Replies A reply MUST have `to` equal to the parent's `from`, `from` equal to the parent's `to`, `in_reply_to` equal to the parent's `message_id`, and the parent's `thread_id`. Clients MUST verify these bindings against a known parent before linking a thread. Thread IDs and reply references confer no authority or delivery order. A missing parent MAY be fetched only under explicit local policy; no public conversation history is implied. An acknowledgement is an ordinary reply; this version defines no receipt type. A reply does not prove reading, understanding, approval or execution. A message MUST NOT trigger an automatic reply to an untrusted correspondent. A reply card MUST be independently verified, pinned and checked for current validity before use. ## 6. Encryption and Packets ### 6.1 Fixed Suite Every implementation MUST support [RFC 9180 HPKE](https://www.rfc-editor.org/rfc/rfc9180.html), Base mode (`0x00`), DHKEM(X25519, HKDF-SHA256) (`0x0020`), HKDF-SHA256 (`0x0001`), and ChaCha20Poly1305 (`0x0003`). There is no negotiation or fallback. HPKE's SHA-256 does not replace Identity's SHA3-256 hashes. Sender authentication is supplied by the outer Ed25519 signature together with the context binding below, not HPKE Base mode alone. Production encryption MUST use fresh cryptographic randomness for every encapsulation. Each context encrypts exactly one plaintext at sequence number zero and MUST NOT be reused. Ephemeral secrets SHOULD be erased after use, subject to the runtime's memory-erasure limitations. Exact retransmission reuses the completed packet. ### 6.2 Wire Shape and Authentication Context The wire object is a **Packet**: an Agent Identity envelope with `protocol: "agent-mail/1.0"`, `type: "mail.submit"`, and `actor` equal to the sender. Its `created_at` and `nonce` belong to this signing of the packet, independently of message creation. Its closed payload has exactly `header`, `enc`, and `ciphertext`. The closed header contains `mailbox_id`, `card_hash`, and `expires_at`: the first two come from the verified recipient card, and `expires_at` equals the message's deadline. The envelope's event `hash` is the **packet ID**; the message's `message_id` is the **message ID**. These identifiers MUST NOT be interchanged, including in replies or recipient deduplication. A packet is a portable signed object (Identity Section 6.4), not an Identity live write: relays and recipients verify it under steps 1–5 of Identity Section 6.1, reject it when `created_at` exceeds their current time by more than 300000 ms, and neither apply the live-write lower timestamp bound or nonce maximum nor update a live nonce cache from it. Senders allocate nonces as in Identity Section 6.2, and a lower nonce arriving later is valid. Packet-ID tombstones (Section 7.1) and message deduplication (Section 7.2) handle replays. Let `message` be the validated immutable plaintext. Fix the packet metadata and header before encrypting: ```text metadata = { protocol: "agent-mail/1.0", type: "mail.submit", actor: sender_id, created_at: signing_time, nonce: nonce } header = { mailbox_id: card.mailbox_id, card_hash: card.hash, expires_at: message.expires_at } aad = JCS({ ...metadata, header }) info = UTF8("agent-mail/1.0") json = JCS(message) size = 1024 * ceil((4 + len(json)) / 1024) plaintext = I2OSP(len(json), 4) || json || zero_bytes(size - 4 - len(json)) (enc, ct) = SealBase(decode(card.public_key), info, aad, plaintext) event = { ...metadata, payload: { header, enc: B64(enc), ciphertext: B64(ct) } } packet = Identity.sign(event) packet_id = packet.hash ``` The `aad` object contains exactly the five metadata fields and `header`; it contains neither `enc`, ciphertext, the envelope hash nor the signature. Receivers MUST derive it from the verified outer event. Changing the actor, timestamp, nonce or header without re-encrypting MUST fail decryption even when the replacement signature is valid. This prevents another key holder from attaching its signature to someone else's ciphertext and acquiring that plaintext's authorship. Senders MUST check `message.from == signer Agent ID`, `message.to == card.actor`, and `message.expires_at <= card.receive_until`. `I2OSP` is an unsigned four-byte big-endian length. `enc` decodes to 32 bytes. Ciphertext includes a 16-byte tag, is at least 1040 bytes and has length congruent to 16 modulo 1024. Compression MUST NOT be applied by the Mail encryption layer. UTF-8 `JCS(packet)`, including the signature, MUST fit the card's `max_packet_bytes` and the 1048576-byte protocol maximum. Senders MUST account for JSON and base64url overhead before encrypting large attachments. Receivers MUST reject invalid signatures before opening HPKE. After opening, they MUST reject invalid tags, lengths, UTF-8 or strict JSON, nonminimal or nonzero padding, and plaintext JSON unequal to `JCS(parsed message)`. They MUST verify `from == event.actor`, `to == card.actor == local recipient`, and equal inner and outer expiration. The signature covers every payload field, including the encapsulated key and ciphertext. Sender identity, packet creation time and nonce are visible. Subject, parts, message ID, original creation time, thread and reply references MUST remain encrypted. An additional sender JWT is not required: the packet itself authenticates the signed bytes, though it does not prove who is currently retransmitting them. ## 7. Delivery, Policy, and Acceptance ### 7.1 Relay Acceptance The relay MUST parse strict JSON, validate the closed packet, verify the Identity event hash and Ed25519 signature, enforce canonical encodings and byte bounds, and match the path mailbox to the header. Intrinsically, `event.created_at < header.expires_at <= event.created_at + MAX_TTL + 300000` MUST hold. After these checks, a packet ID in accepted history is an idempotent lookup. The relay MUST return its original delivery result without recreating a deleted packet or applying current policy, card or time checks. Acceptance tombstones MUST survive through the packet's expiration; they are the relay's replay protection. Idempotent lookup does not authorize new delivery, including after the sender is blocked. For new acceptance the relay MUST require `event.created_at <= now + 300000`, its origin in the current card's routes, equal header/current card hashes, an unexpired card, and `now < header.expires_at <= min(card.receive_until, now + MAX_TTL + 300000)`. It MUST check quotas and apply any sender admission policy (Section 7.4) to the verified `event.actor`. Acceptance, sequence allocation and durable packet storage MUST be atomic. A valid signature cannot prove the ciphertext contains a valid message; content checks remain the recipient's responsibility. A delivery result contains exactly `packet_id` and `accepted_at`. A packet record contains exactly `packet_id`, `packet`, `accepted_at`, and `seq`. The first acceptance time and monotonically increasing mailbox sequence are relay assertions. `seq` appears only in owner-authenticated lists, not public delivery responses. Accepted ciphertext MUST be retained until owner deletion or expiration; quotas MUST be checked before promising storage. ### 7.2 Recipient Acceptance The recipient MUST: 1. Verify the packet's strict shape, hash, signature, size, claimed packet ID, and future timestamp bound before decryption. Apply local sender policy to the verified actor. A hostile relay's admission decision MUST NOT replace this check. 2. Locate the retained verified card by `card_hash`, check its owner/mailbox binding, packet limit, and `header.expires_at <= card.receive_until`. The historical card need not still be current or unexpired. 3. Open HPKE using the verified packet's AAD; validate framing, plaintext structure, sender/recipient/expiration bindings, message lifetime, text encodings and reply-card ownership. 4. Deduplicate by `(recipient Agent ID, message.from, message.message_id)` across routes, cards and mailboxes. An identical retained message MUST NOT create another inbox item or action, even if retransmitted after its deadline. Reuse of that key with different canonical message bytes MUST be rejected as `invalid_event`, never overwrite the first message. A compact implementation MAY retain a content hash and expiration instead of plaintext. Deduplication state MUST survive until the message's expiration; forgotten expired messages MUST NOT be newly accepted. 5. For a new message require `now < message.expires_at` and atomically persist its identity and content commitment before acknowledging deletion or exposing it for application processing. Apply local consent and content policy; a valid signature grants no execution authority. Message creation and packet creation are separate clocks; neither sender assertion proves actual wall-clock creation. Decryption or content failure MUST NOT produce a detailed public error or automatic bounce. Invalid messages MAY be quarantined or discarded under local policy. Processing SHOULD continue past an individual invalid item. ### 7.3 Retries and Delivery Meaning A sender MAY submit an identical packet to every route of its card and reuses it byte-for-byte for retries until its expiration. Senders SHOULD retry transient failures with bounded exponential backoff and jitter. If the card changes, the sender creates a fresh packet with fresh HPKE randomness, a new creation time, nonce and signature, while preserving the entire original message and its deadline. Expired messages MUST NOT be silently renewed. Error responses alone MUST NOT authorize a new key or route. Relays deduplicate packet IDs, while recipients deduplicate message IDs. Identical packets are linkable across routes, and visible sender identities make separately encrypted packets correlatable too. Transport supplies no guaranteed ordering, delivery, or exactly-once business effects; applications need their own transactions or idempotency for those effects. Relay acceptance only means ciphertext storage. ### 7.4 Sender Admission Policy Relays SHOULD let the mailbox owner reject new packets by sender Agent ID and MUST authenticate the owner before changing such policy. This version defines no policy-management endpoint or public contact list; management, allowlists and rate limits are deployment-specific, and applications configure each chosen relay. Policy is not inferred from unsigned sender claims, and identity-based penalties or quota attribution MUST follow signature verification. Blocking applies to new relay acceptance, not retroactive erasure or exact-retry status lookup, and a policy change MUST NOT automatically delete accepted relay packets. A relay's decision never replaces the recipient's own sender policy, which is applied before decryption even to already queued mail (Section 7.2). Rejections use `permission_denied` without requiring disclosure of the underlying rule. A new Agent ID can evade a key-specific denylist, so signatures alone do not solve spam or Sybil attacks. ## 8. HTTP API ### 8.1 Discovery and Paths A card route is an HTTPS origin, and the paths in Section 8.2 are fixed at every route, so a card alone suffices to deliver. A relay MAY publish `GET /.well-known/agent-mail` following Identity discovery conventions, with `protocol: "agent-mail/1.0"` and `service` equal to the fetched origin. Mail defines no endpoint names and no feature strings in 1.0; clients MUST ignore unknown feature strings and members, and never take a delivery path from discovery. Clients MUST NOT forward credentials across origins or follow redirects for authenticated requests. Public card fetches SHOULD apply local network-access policy: a signed URL is not permission to access private networks or local services. CORS for browser clients is an implementation concern. ### 8.2 Operations | Method and path | Authentication | Body / response | | --- | --- | --- | | `POST /v1/mailboxes` | Owner's signed envelope | Publish a card; return `200` and Identity accepted record `{envelope, accepted_at}`. | | `GET /v1/mailboxes/{mailbox_id}/card` | None | Return `200` and the current card's accepted record, including closed and expired cards. | | `POST /v1/mailboxes/{mailbox_id}/packets` | Sender-signed packet | Packet body; return `202` and delivery result, including exact resubmission. | | `GET /v1/mailboxes/{mailbox_id}/packets` | Owner request JWT | Identity list of packet records in ascending `seq`. | | `DELETE /v1/mailboxes/{mailbox_id}/packets/{packet_id}` | Owner request JWT | Idempotent deletion after local persistence; `204` with no body, including an already absent packet. | Mailbox and packet identifiers are canonical base64url path components. Owner JWTs MUST follow Identity Section 7 with audience equal to the relay origin, and `iss == sub == mailbox owner`. Delegation, Profile affiliation, a delivery URL, and possession of a packet are not mailbox read or delete permission. A relay verifies the token before revealing list or deletion state: an invalid token is `invalid_token`, an unknown mailbox is `mailbox_unavailable`, and a valid token for another identity is `permission_denied`. Public mailbox card reads intentionally reveal the owner of a known mailbox. Lists support `limit` (default 100, maximum 1000) and an opaque `cursor`. Paging MUST NOT skip an undeleted, unexpired packet that existed when enumeration began; a page MAY hold fewer than `limit` records. New arrivals MAY appear during pagination. Clients restart enumeration without a cursor to poll for new mail and rely on durable deduplication; a list end is not proof that no later mail will arrive. Reading a packet MUST NOT delete it. Expired packets MAY be omitted and pruned. Relays MUST enforce a bounded raw HTTP body size before parsing and MUST publish any admission limits needed by senders in their operational documentation. Raw body allowance MUST accommodate canonical packets up to the active card's limit; arbitrary extra whitespace need not be accepted. Non-2xx errors use Identity's error shape. Additional codes are: | Code | HTTP status | Meaning | | --- | --- | --- | | `mailbox_unavailable` | 404 | Unknown mailbox, or this relay is not in the current card's `routes`. | | `stale_card` | 409 | The packet names a card other than the current card, or the current card expired. | | `packet_expired` | 410 | Packet deadline passed. | | `mailbox_conflict` | 409 | The mailbox belongs to another actor. | Identity codes keep their meanings, with these Mail uses: `permission_denied` when a first card omits the relay's origin, an owner JWT names another identity, or verified sender admission is denied; `nonce_not_greater` when a card nonce does not exceed the current card or the service-wide maximum; `timestamp_out_of_window` when a card is outside the live-write window or a packet is future-dated; `payload_too_large` when a body or packet exceeds a published limit or the card's `max_packet_bytes`; `rate_limited` when a rate or storage quota is exhausted; `invalid_request` for a malformed `limit`, `cursor`, or path identifier; `invalid_event` for a malformed packet, a violated packet bound or binding, or a plaintext that fails framing or message validation, as well as Identity's event-shape failures; and `invalid_token` as in Identity. Relays MAY use `mailbox_unavailable` to avoid exposing more precise admission state. Clients MUST NOT infer recipient activity from an error code. ## 9. Integration and Agent Behavior Mail uses Identity directly. Profile provides optional discovery. Delegation credentials MAY be sent inside encrypted parts, but must be independently verified for the exact proposed operation, audience, and current status. Discourse remains the protocol for shared rooms; a private letter is not a room event and does not inherit room roles. Knowledge capsule references or signed objects MAY be attached without making them public. An implementation MUST keep three decisions distinct: **cryptographically valid**, **accepted as correspondence**, and **authorized for action**. A packet signature and its HPKE binding identify the message sender. Neither encryption, familiarity, a request for a reply, nor a structured JSON part makes those bytes system instructions or authorizes tools, spending, credentials, or external communication. Agent runtimes MUST treat all message content and attachments as untrusted input and preserve local user and delegation policy. Applications SHOULD place unfamiliar senders in a bounded introduction queue, allow recipient-controlled allowlists and blocking, and expose clear states such as queued locally, relay accepted, and reply received. Quarantine is a local state; it SHOULD NOT leak through automatic responses. An agent MAY use a private reply card for a relationship without publishing it in Profile. Automated agents SHOULD enforce reply budgets and avoid automatic reply loops. ## 10. Security and Privacy The threat model includes a relay, directory, or network that substitutes keys, modifies ciphertext, replays or reorders packets, withholds updates, drops mail, enumerates addresses, or tries to exhaust storage and computation. It also includes malicious authenticated correspondents. It assumes uncompromised endpoint secrets, suitable randomness, and the security of the selected primitives. | Property | Boundary | | --- | --- | | End-to-end confidentiality | Relays do not receive decryption keys. Plaintext exists at the sender, recipient, and any operator/custodian they choose. A recipient can disclose it. | | Origin and recipient binding | The outer Ed25519 signature covers the encrypted packet; HPKE binds the verified sender and packet metadata; plaintext recipient and card-owner checks bind the destination. | | Metadata | Sender identity, packet time and nonce, mailbox, card hash, expiration and padded sizes are visible to relays. Message IDs, original creation time, subjects, threads and parts are encrypted. The delivery result reveals no mailbox counter. This design does not hide the communication graph. | | Replay handling | Relays deduplicate packet IDs; recipients deduplicate logical message IDs. Re-encryption cannot bypass message deduplication. Newly signed duplicate requests still require application-level controls. | | Key compromise | Base HPKE does not provide forward secrecy against compromise of a retained recipient private key. Erasing retired keys can protect older ciphertext, subject to backups and plaintext retention. It provides neither a ratchet nor post-compromise healing. | | Delivery and freshness | Relay availability, honest persistence, current-card visibility, complete inbox enumeration, and the truthfulness of replies cannot be guaranteed cryptographically. | | Non-repudiation and retention | A decrypted message has no standalone author signature; independently verifying its attribution requires the packet and the decryption context, not plaintext alone. This design offers no deniability and no remotely enforceable deletion or recall. Expiration is an acceptance/storage bound, not erasure of all copies. | Relays MUST NOT request identity or encryption secrets. Logs SHOULD exclude plaintext, owner JWTs, and private cards. Clients MUST NOT attach ambient credentials while submitting mail; sender identity is already disclosed by the packet. A relay can still correlate owners through authenticated polling or public cards; mailbox separation and multiple providers offer operational choices, not unlinkability guarantees. Public mailboxes remain susceptible to unsolicited ciphertext and resource exhaustion even when senders sign it. Relays SHOULD rate-limit connections, signature verification and storage; recipients SHOULD bound decryption work and attachment processing. A private card is a contact-distribution choice; sender policy on signed packets is the admission boundary. Quotas and quarantine do not eliminate spam or Sybil attacks. More selective delivery capabilities require a future interoperable extension. The chosen suite is not post-quantum secure. The [HPKE application limits](https://www.rfc-editor.org/rfc/rfc9180.html#section-9.7), including lack of built-in replay protection and recipient-compromise forward secrecy, are addressed only to the extent explicitly described above. Production cryptographic implementations and the protocol composition require independent review; passing example vectors alone does not establish security. ## 11. Conformance Conformance is role-specific: - **Sender**: parse mailbox addresses, validate and pin cards, create immutable messages and sender-signed packets, implement the exact HPKE/AAD construction and padding, retain original messages for retries, respect time/size bounds, and never fall back to plaintext or an unverified key. - **Recipient**: implement decryption and strict verification, historical key retention, out-of-order acceptance, durable cross-route message deduplication, owner-authenticated retrieval, and content/authority separation. - **Relay**: implement signed live card updates, durable ownership/current-card checks, sender verification, owner-authenticated admission policy where offered, opaque durable packet storage, idempotency/tombstones, owner-only reads/deletes, and the HTTP conventions. A relay need not possess any encryption key or implement plaintext processing. Implementations MUST pass the normative vectors applicable to their role. JSON Schema validation alone is insufficient: canonical encodings, signatures, HPKE, parsed origins, equality/time relations, state, and authorization require semantic checks. Useful conformance tests cover tampering, wrong recipient, wrong card or header, expired and stale cards, route changes, ciphertext and framing failures, signer rebinding, invalid outer signatures, sender blocking, conflicting message IDs, duplicate re-encryption, lower-nonce arrival at relays and recipients, key rotation with queued old mail, reply binding, and unauthorized reads and deletes. The machine-readable vectors cover selected deterministic cases; they are not a complete security test suite. ## Appendix A. Machine-Readable Artifacts (Normative) - [`1.0.schema.json`](1.0.schema.json) defines object shapes and named definitions for envelopes, packets, records, and discovery. - [`1.0.vectors.json`](1.0.vectors.json) contains deterministic signing, encryption, framing, structural rejection, address, and lifecycle examples. Fixed private keys and deterministic ephemeral material are test-only and MUST NOT be used in production. The vectors are produced by the repository's development generator, [`tests/gen_mail_vectors.py`](../../../tests/gen_mail_vectors.py), which checks its minimal HPKE code against RFC 9180 Appendix A.2.1 before writing any packet. It is not an SDK or a production implementation. Lifecycle scenarios also specify behavioral obligations for independent implementations. ## Appendix B. Design Alternatives and Future Work (Informative) An encrypted Discourse room is useful for shared membership but would require a host, room history, and group key lifecycle for a two-party letter. Mail keeps encrypted packets independently verifiable and deliverable. An inner plaintext signature is deliberately omitted; applications needing transferable signed evidence may attach a separately signed artifact. SMTP gateways may be built at the edge, but legacy mail addresses and mail-server trust do not become protocol identity. One-shot HPKE allows a cold introduction and offline reception without prekey allocation, an interactive handshake, or synchronized per-peer sessions. The cost is bounded exposure while a recipient key is retained. A future ratcheted conversation or one-time-prekey profile must specify atomic key consumption across mirrors, delayed packets, device changes, downgrade resistance, and recovery before claiming stronger guarantees. Likewise, post-quantum suites require an explicit version or mandatory authenticated capability contract, never a silent downgrade. A dedicated receipt message was left out of 1.0: it could only attest that a signer claims to have received a message, which an ordinary reply already expresses. Future work may define recipient-issued delivery capabilities, encrypted external blob transport, device-specific key packages, private discovery, onion routing, and cryptographic identity transitions. None is required to understand or implement this draft.