Mxr Docs Site

Mxr docs site

The mxr docs site is the public documentation site for Mxr. It lives in site/, uses Astro Starlight, and is configured for https://mxr.sh. The old mxr-mail.vercel.app hostname redirects there.

It exists because a local-first tool still needs public, operational docs. Users need install paths, setup recipes, config defaults, bridge auth, and CLI examples. Agents need stable reference pages and JSON shapes. Contributors need enough architecture to avoid accidentally breaking the daemon/client boundary.

What it ships

Why this shape

The site follows the repo docs philosophy: command first, explanation second; every shipped surface gets a page; composition gets recipes; reference is generated when the code can provide the truth.

Generated docs are the drift defense. The CLI reference comes from clap help. The OpenAPI surface comes from the bridge spec. Hand-written pages still matter, but they should explain how to use the surfaces, not copy tables that the code can generate.

Hand-written examples are where semantic drift hides. In August 2026 the issue-179 docs sweep found about 40 from:/to: examples across 11 pages that assumed partial-address matching (from:sarah, even from:no-reply@*.example.com), a behaviour the engine never had: those operators are exact-address, case-insensitive term queries. Every example was a small lie that no build step could catch, because examples are prose to the generator. The repair was two-part: fix the examples to whole addresses, and state the matching rule once on the operator reference page so future examples have a source of truth to disagree with visibly. When an engine's semantics live only in examples, every example is an independent chance to drift.

One correction from the security audit: the live bridge's OpenAPI and Swagger routes are authenticated. The site can publish reference docs publicly, but examples that fetch a live local spec need to pass the bridge token or dump the spec from the Rust example.

2026-05-28 follow-up: the bridge docs also needed to name /api/v1/i18n as an unauthenticated read-only bootstrap route. The code already exposed it for the SPA locale bundle; the docs only named /health and /auth/local-token. This is the quiet kind of drift generated docs do not catch unless the route is included in the generated spec.

2026-05-31 follow-up: the docs pass found a different kind of drift. Public docs still treated bundled Gmail OAuth as an alpha-only convenience, listed gmail.send as a requested scope, and left the legal terms page as a stub instead of the public policy surface. Code truth said otherwise: release builds can include bundled Gmail credentials when compiled with GMAIL_CLIENT_ID and GMAIL_CLIENT_SECRET; the current Gmail scope set is readonly, modify, and labels; and the docs site is the public OAuth-policy surface.

Same pass, the vault had stale desktop truth. Older notes said apps/desktop/ existed. The repo only has apps/web/; the public site now has an explicit "No native desktop app" page. The desktop lesson still matters, but it is historical now.

The release docs also needed the current deployment story. The docs site is built in CI and deployed by Vercel on pushes to main; release.yml does not deploy docs to Cloudflare. Legal page sync is manual today, not a build-copy step.

Validation found one generated drift too: npm run build regenerated site/public/openapi.json from 0.5.47 to 0.5.49. The API surface was not wrong, but the published spec metadata lagged the released binary.

2026-06-04 follow-up: the docs site needed a package-docs pass after the
extracted crates received docs/metadata patch releases. The public Rust crates
guide now tells readers to check mxr's consumed dependency version separately
from the latest crates.io release. The "Why mxr" page also moved away from
competitor tables and into fit, non-goals, provider capabilities, and current
integration surfaces.

2026-06-09 follow-up: the site still had two post-v1 drifts. First,
site/public/openapi.json said 0.5.58 while the repo and release tags had
reached 0.5.61; regenerating from mxr-web fixed the metadata without a
hand edit. Second, the security/privacy guide still listed first-party MCP,
agent read-only/draft-only modes, account-scoped agent permissions, send
approval, and command blocking as "not shipped yet." Code truth says these now
exist as daemon profile gates for agent and mcp origins, with mxr mcp serve as the first-party MCP surface.

2026-06-24 follow-up: the docs site was mostly current, but the activity guide
and security/privacy guide had drifted in small, consequential ways. The
activity guide still implied new IPC verbs defaulted to no activity; code truth
now says the mapper has no wildcard and each request must be classified. The
same page overstated the PII test as if it covered every mapper shape, when the
current test covers representative shapes and needs new fixtures for sensitive
requests. The security/privacy guide already mentioned attachment sanitization,
but did not say cache filenames include the attachment id to avoid same-name
collisions.

2026-07-22 follow-up: a production keychain incident had aged into historical
evidence. The runtime-identity lesson still held, but current mxr no longer
reads IMAP/SMTP passwords eagerly during daemon construction. Version 0.6.11
moved those passwords to a disk-first secrets.toml with lazy keychain fallback;
0.6.12 made accounts repair work without a running daemon. The public security,
config, troubleshooting, and reset docs now name the storage location, lookup
timing, MXR_KEYCHAIN=off, daemon-independent recovery, and credential
preservation explicitly.

The same code check caught a separate repair-path drift. accounts repair only
handles password-backed IMAP/SMTP accounts; Gmail and Outlook use accounts reauth. The troubleshooting and bridge reference pages had blurred those two
paths and now describe them separately.

The generated CLI reference still says accounts repair writes to a protected
keychain store because that wording comes from the Rust help string. The
generator is behaving correctly; its source text has drifted. Fixing that needs
a code change and a regenerated reference, so it remains outside this
documentation-only pass.

The same day's alias check found a different boundary. Code and the installed
0.6.12 binary support per-message sending from registered aliases. The
compose and accounts guides already described the feature, but they blurred
local registration with provider permission. The docs now say that mxr accounts addresses add registers an identity inside mxr; Zoho, Gmail, or the
SMTP provider must still create and authorize it. Active Markdown links also
use the canonical mxr.sh domain now.

2026-08-12 follow-up: the draft-edit docs are current in 0.6.19. The
installed binary, README, generated drafts reference, compose guide, CLI source,
and store tests all agree that mxr drafts edit DRAFT_ID edits in place and
keeps the linked provider draft. Any note that says to create a corrected draft
and discard the old one is historical now.

The SEC receipt check also found a body-source edge. A message can have no PDF
attachments and still have provider body evidence that mxr cat does not
surface if the cached body is a best-effort placeholder. The docs site already
explained the TUI/ListBodies local-read boundary; the mailbox guide now names
the CLI repair case as missing, legacy, or suspicious best-effort rows.

2026-09-04 follow-up: the campaign recordings now have a permanent home at
/videos/. The gallery uses 15 synthetic-mail MP4s with posters and no audio.
Each player uses preload="none", so opening the page does not download every
video, and each fallback link names its clip. This turns a private batch of
marketing assets into a documented product surface without creating a new app
or release artifact.

This does not complete the earlier Twitter posting plan. The local automation
registry had no mxr or Twitter publishing job on 4 September 2026. A stable
gallery and a distribution schedule solve different jobs. See A Content Plan Needs an Execution Loop.

The same pass found another generated OpenAPI drift. The checked-in spec still
said 0.6.24; generation from main at 0.6.30 also exposed a new IMAP
max_connections field and corrected account-repair and sync-progress text.
Generation found the differences. The docs pass still had to explain the
four-connection default and minimum in the hand-written config reference.

2026-09-23 follow-up: a completed travel workflow exposed an agent-doc gap. Mail can prove what a provider sent, booked, or charged at a point in time. It cannot prove a live flight status, current policy, claim result, or final ledger balance. The agent skill and public agent guide now state that boundary. The recipes guide also documents an evidence-pack workflow that keeps missing and contradictory records visible.

2026-09-24 follow-up: the previous documentation existed on codex/travel-doc-closeout but had not reached main. The agent rule and evidence-pack recipe were applied to current main at dfb23d10. The expense reconciliation added a second boundary: mail can collect receipts and invoices, but an expense total must match those records to economic events and use the bank or card ledger for posted transactions. See Reconcile economic events, not transaction rows. The checked-in OpenAPI spec still reports 0.6.24 while the workspace is 0.6.31. A current exporter build did not complete during this pass, so the stale spec was preserved and the drift is tracked in docs/issues/openapi-version-drift.md.

Code grounding

Lessons

  1. Docs drift less when code generates the boring reference parts.
  2. A docs site for a CLI product needs recipes, not just flag lists.
  3. Public docs and agent docs are closer than they look: both need exact commands and stable output shapes.
  4. Internal blueprint docs need their own drift checks. They can go stale even when the published site is healthy.
  5. Security fixes need docs-site follow-through. If the bridge auth model changes, the public curl examples have to change with it.
  6. Agent-context changes need docs-site follow-through too. The canonical skill source is .agents/skills, while .claude/skills is a compatibility symlink. Public install docs should name the canonical repo path even if the local Claude install path stays ~/.claude/skills.
  7. Packaging docs are source-of-truth docs too. The current workflow builds Linux x86_64 and macOS Apple Silicon archives. Install pages should list exactly those targets, not the targets an older roadmap hoped to support.
  8. A tag is not the same as a binary release. scripts/release_change_scope.sh can create a docs-only GitHub Release with no tarballs and no Homebrew update. That is fine, but the release docs have to say it plainly or the verification checklist becomes a lie.
  9. Preview surfaces deserve docs before they deserve launch language. If a desktop wrapper returns, the doc should say what works, what is missing, and which artifact a user can actually trust today.
  10. OAuth setup docs should follow the honest first-run path. For mxr v1, BYOC is the production-safe Gmail path; bundled Gmail OAuth is useful only as an unverified fallback until the shared app is verified.
  11. Internal blueprint docs drift too. A checked-in workflow comment saying "Vercel auto-deploys" beats an older blueprint snippet about Cloudflare release deploys.
  12. Generated artifacts still need to be regenerated after releases. A stale OpenAPI version number is small, but it tells agents and API explorers they are looking at an older surface.
  13. Package docs need version-surface labels. The crate registry can move while
    mxr stays pinned to the same dependency.
  14. Positioning docs age better when they describe fit and non-goals instead of
    naming opponents.
  15. Generated docs need release-time ownership, not just a build command. If a
    release PR bumps the product to 0.5.61, the checked-in OpenAPI metadata
    should not still say 0.5.58.
  16. Security pages age badly when they keep a "not shipped yet" list. After a
    feature crosses into daemon-enforced behavior, rewrite the section around
    the current boundary and the remaining caveat.
  17. Status docs should distinguish completion from activity. A sync with
    last_synced_count: 0 can still be a clean completed sync; readers should
    check last_success_at and sync_in_progress when they are diagnosing
    whether the daemon actually ran.
  18. Generated reference can be correct while hand-written examples drift. On
    22 July 2026, clap-derived account-address reference used --account, but
    the accounts guide and agent command reference still showed the retired
    positional form. Example commands need validation too, especially when
    they restate a generated surface.
  19. Credential docs need to explain timing and recovery, not only storage. Where
    a password lives does not tell a user whether one bad account can block
    startup or whether repair depends on the failed daemon.
  20. Generated reference can faithfully publish stale source prose. Generation
    removes copying drift; it does not make an outdated help string true.
  21. Identity docs need two authorities. A local registry can decide which
    sender an app will attempt to use; the provider still decides whether that
    account may use it on the network. See Local Identity Registration Is Not Provider Authorization.
  22. A product can have two fields that both look like "primary" without them
    driving the same behaviour. In mxr 0.6.12, the configured account email is
    the account-key default From, while the owned-address registry has its own
    is_primary marker. Documentation must name the field and the behaviour it
    controls instead of saying only "primary address".
  23. Body docs should name the evidence surface. Attachment absence, cached
    reader output, raw HTML, and provider payload are different claims.
  24. Media becomes a shipped docs surface when it has a stable URL, navigation,
    context, and an owner. Files in a private folder are assets, not a gallery.
  25. A gallery can be small and still need performance and accessibility review.
    Posters plus preload="none" defer downloads; clip-specific link text keeps
    repeated controls distinguishable.
  26. Generated drift often carries more than a version bump. Regenerating the
    0.6.30 spec also surfaced a new config field and corrected behavioral text.