Adds a JMAP (RFC 8620 core, RFC 8621 mail, submission) endpoint so mail
apps such as Mailtemi, Twake Mail and aerc can read and send Mailflare
mail. Discovery at /.well-known/jmap, session at /jmap/session, API at
/jmap/api, blobs at /jmap/download and /jmap/upload, push at
/jmap/eventsource.
- Auth: an API key with the new `jmap` scope, as a Bearer token or as the
password of HTTP Basic auth. `authenticateApiKeyValue` is split out of
the Next-bound `src/lib/api/auth.ts` into a framework-free module and
refreshes `lastUsedAt` at most once a minute, since protocol clients make
many small calls.
- Model: the account is the user. Each Mailflare mailbox is a top-level
JMAP Mailbox with six system children keyed by status and one child per
user folder; ids encode `mailboxId`, `mailboxId~role` or
`mailboxId~f~folderId`. A message is in exactly one of them.
- Methods: Mailbox/get|query|set (folders only), Thread/get,
Email/get|query|set, SearchSnippet/get, Identity/get,
EmailSubmission/set, Core/echo. Every */changes answers
cannotCalculateChanges (messages have no change log), so clients
re-query when the state string moves; eventsource emits StateChange by
polling the state digest.
- Email/query maps FilterCondition and AND/OR/NOT trees onto message
columns, supports collapseThreads, anchors, position and calculateTotal.
Email/get builds objects from stored rows (no MIME parsing), with
bodyValues, bodyStructure, attachments as blob ids, and header:X forms
answered from stored headers.
- Email/set updates $seen/$flagged, moves via mailboxIds or
mailboxIds/<id> patches, creates drafts (with uploaded attachments), and
destroys to Trash first and permanently from Trash.
EmailSubmission/set sends a draft through the existing send path and
reports it destroyed via the implicit Email/set response, since the sent
copy is a new row. Result references (#ids with resultOf/name/path,
including the * form) and #creationIds work within a request.
- Settings > Account > Email apps mints a jmap-scoped key and shows the
server details once. docs/api.md and CLAUDE.md describe the mapping.
Verified with a 47-check script on local D1: discovery redirect, 401/403
paths, Basic and Bearer auth, capabilities, unknown method/capability
errors, mailbox tree and counts, folder create/rename/destroy and system
mailbox protection, query with collapseThreads and result references,
attachment/text/operator filters, bodies with truncation, blob download
of an attachment and of a raw message, Thread/get, snippets, keyword and
mailbox patches, ifInState, upload, Identity/get, a draft created and
submitted in one request with #d1, the sent copy carrying cc and the
attachment, two-step destroy, and eventsource. tsc (11) and eslint (105)
unchanged from main.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>