Fastmail API Quirks
Everything the session learned the hard way, all encoded in code and tests:
- 50-item search cap (MCP).
search_emailhard-caps at 50 per page; JMAP pages at 100. Expressed ascaps.maxPageSize, clamped everywhere. - Array vs
{ items }. The MCP server answers either with a bare array or{ items: [...] }for the same tool. Adapters normalize both. - ISO-only
after:.after:YYYY-MM-DDis the only date operator Fastmail search reliably accepts. -from:is literal-address-only. Domain negation is silently ignored — keep-lists must be re-checked client-side (caps.serverSideNotFrom: 'address-only').- Spaced values need quoting in the search DSL. Unquoted,
in:Needs actionparses asin:Needsplus a free-text termaction— silently wrong scope. The query builder double-quotes any value containing whitespace. addLabelsauto-creates labels (MCP). There is no create-label tool, butupdate_emailaddLabelsauto-creates missing labels.ensureLabelspre-flights stay as typo protection.- Archive ≠
removeLabels: ['Inbox']. The server rejects removing Inbox viaremoveLabels;archive_email(MCP) / the paired inbox-null + archive-true patch (JMAP) is the sanctioned path. - Label color is not settable via JMAP. The session tried; Fastmail ignores it.
- Distinct tokens. The JMAP API token and the MCP endpoint token are different credentials.
rateLimitSetError backoff. Fastmail signals per-record throttling as aSetErrorof typerateLimitinnotUpdated/notCreated, not HTTP 429. Both are converted into the same retry/backoff path.- MCP handshake details. Capture-and-echo
mcp-session-id, dualAcceptheader required, SSEdata:frames even for single results,structuredContentunwrap with text-body fallback, rate limiting reported as a tool error text, not a status code. - JMAP sort tie-break limitation. No total-order tiebreaker exists among JMAP’s standard sort properties, so equal
receivedAtvalues at a page boundary can reorder between calls; the pager’s seen-set and audit-resume pattern recover anything stepped over.