Integrate the mail API and gateway handoff

The mail API is tenant-bound and uses standard Workspace permissions. Clients receive mail content only through ACL-checked Storage URLs.

This reference is for developers of mail work surfaces and private gateway adapters. It defines the authoritative HTTP and SMTP contracts. Follow Provide mail through an SMTP gateway for operational setup.

Check the contract with nucli

Use nucli so authentication, tenant selection, and permissions remain in the server contract. The built-in skill documents the safe sequence for read-only checks and explicitly approved mutations:

bash
nucli skills show mail
nucli --tenant <tenant> whoami --scopes
nucli --tenant <tenant> api GET /api/v1/mail/mailboxes --summary
nucli --tenant <tenant> api GET \
  '/api/v1/mail/threads?class=screener&limit=25' --summary

Pass request bodies with --input <file> or --input -. Do not expose subjects, bodies, addresses, notes, Storage URLs, or attachments in agent logs. Use --summary when only HTTP and response metadata are required.

Connect a work surface

  • GET /api/v1/mail/mailboxes returns visible personal and shared mailboxes.
  • GET /api/v1/mail/threads accepts class, mine, limit, and offset.
  • GET /api/v1/mail/threads/{id} returns work state and Storage references.
  • GET /api/v1/mail/search?q=… uses the permission-checked blind index.
  • POST /api/v1/mail/send stores RFC822 and its projection encrypted and queues the same Storage reference once per envelope recipient. Pass readable attachments as attachmentObjectIds.
  • /api/v1/mail/drafts manages encrypted, author-only drafts.
  • /api/v1/mail/threads/{id}/notes manages encrypted internal notes.
  • POST /api/v1/mail/threads/{id}/assist returns only an assistive summary and reply suggestion through the MAIL_ASSIST policy task.

Seen state, move, take/assignment, screening, follow-up, and explicit Inbox projection commands live under /api/v1/mail/threads/{id}/….

Use GET /api/v1/mail/imported-containers to list imported source folders. GET /api/v1/mail/imported-containers/{containerID}/thread-pages returns stable ACL-filtered pages for one folder. These folders are a read-only source view and remain separate from normal work classes.

Compose and send mail safely

The interactive composer uses this sequence:

  1. GET /api/v1/mail/compose/readiness checks local prerequisites.
  2. GET /api/v1/mail/compose/templates lists effective templates from the mailbox/compose namespace. It supports locale, q, count, page or offset, and the fixed key ASC order.
  3. POST /api/v1/mail/compose/preview renders the complete message.
  4. POST /api/v1/mail/compose/send receives the same request plus the returned previewDigest.

The digest is bound to its tenant, identity, and input and expires after ten minutes. The server renders again before sending. Repeating an identical confirmed request returns the same result without creating another message. Changing any field requires a new preview.

Attachments require read access, the same tenant, an approved business Storage context, and an allowed scan state when scanning is active. Never copy subject, body, or attachments into logs, audit metadata, Conversation fields, or queue payloads. Use the returned Storage references.

Interpret SMTP responses

The private listener validates recipients during RCPT TO:

  • 250: the recipient is active and routable.
  • 550: the domain, address, mailbox, or bounce recipient is unavailable.
  • 451: a temporary lookup or Storage failure; the gateway must retry.
  • 250 stored after DATA: original, projection, ACL, blind index, and intake ledger are durably published.

The client must connect with TLS 1.3 and an allowlisted mTLS certificate. Do not expose this contract as a public SMTP listener.

Connect a provider-neutral mail transfer adapter

The adapter needs mail:import. This permission does not grant read, search, download, or export access to mail content. Call the endpoints in this order:

  • POST /api/v1/mail/transfer/sessions
  • POST /api/v1/mail/transfer/sessions/{sessionID}/containers
  • POST /api/v1/mail/transfer/sessions/{sessionID}/batches
  • PUT /api/v1/mail/transfer/batches/{batchID}/items
  • POST /api/v1/mail/transfer/batches/{batchID}/reconcile
  • GET /api/v1/mail/transfer/batches/{batchID} until the status is ready
  • POST /api/v1/mail/transfer/batches/{batchID}/publish
  • GET /api/v1/mail/transfer/batches?limit=50 for the tenant-scoped operational view without source identifiers or mail content

The item request uses multipart/form-data. The first part is named metadata and contains JSON. For upsert, the second part must be named message with Content-Type: message/rfc822. Metadata includes containerId, sourceItemKey, containerRevisionHash, contentHash, operation, direction, sizeBytes, internalDate, and the flags seen, flagged, answered, draft, and deleted. A tombstone request ends after the metadata part.

Manifest version 1 uses UTF-8 and lowercase SHA-256 hex values. For each item, calculate:

text
leaf = sha256(sourceItemKey + NUL + operation + NUL + contentHash + NUL
  + decimalSizeBytes + NUL + direction + NUL + utcInternalDate + NUL
  + seen + NUL + flagged + NUL + answered + NUL + draft + NUL + deleted)

Format utcInternalDate in UTC using RFC 3339 Nano. Encode Boolean values as the lowercase canonical text true or false. Opaque source identifiers must not contain U+0000 because NUL separates the fields. Sort the leaf hex values in ascending byte order, join them with LF, and hash the result with SHA-256. This is the folder's expectedManifestHash. For the batch, create one line per folder as containerRevisionHash:folderManifestHash:itemCount:byteCount; sort, join with LF, and hash those lines in the same way. An empty set is the SHA-256 digest of an empty byte sequence. The server persists source keys only as a keyed HMAC, while adapters can still calculate the public manifest digests independently.

Export a snapshot or delta

Export requires mail:export plus at least read access to the mailbox. POST /api/v1/mail/mailboxes/{mailboxID}/exports accepts fromRevision; use 0 for a complete snapshot. The response fixes throughRevision. Poll GET /api/v1/mail/exports/{exportID} until the job is completed and returns an artifactUrl. The encrypted nucleus-mail-transfer.v1 tar contains manifest.json and unchanged RFC822 files. Delta boundaries rely only on monotonic mailbox revisions, never timestamps.

The artifactUrl points to the protected mail endpoint GET /api/v1/mail/exports/{exportID}/artifact. Every download rechecks the requesting identity, mail:export, the current mailbox ACL, and the expiry time. Never copy exports into logs, tickets, or unprotected temporary storage.

The integration succeeds when an authorized client receives only its granted mailboxes and threads, an unauthorized request fails without content details, and the gateway removes a message from its queue only after 250 stored. For outbound mail, distinguish stored, queued, delivered, and DSN-failed recipient state.