Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Serving the browser clients

The webmail app, the name marketplace page, and the three extensions — Chrome, Outlook, Thunderbird — are end-user surfaces. This page is the operator’s half of them: what each one needs of account-api, which bundles you build and mount, and where the two server-hosted pages come from.

The reader-facing pages are under GUI clients, and the install instructions a user follows live there. Nothing on this page is something an end user does.

What account-api must have configured

All five clients talk to account-api and nothing else, so what a pane can do follows that one service’s config:

  • [store] — always. Login, mail reading, and the account settings panes (mail password, timezone, do-not-disturb) need only this.
  • [chain] — a mail-grpc gateway plus a Solana RPC endpoint (see the configuration reference). Without it the aliases, balances, and delegated encryption-key panes report their surface as unavailable, while login and the settings panes keep working. This applies identically to all four clients that carry those panes.
  • [mail] (local_domains, [mail.dkim]) — webmail’s compose path only; see The webmail app below.
  • [tls] — Outlook only, which refuses to load a taskpane over plain http; see The Outlook add-in below.

Every bundle that account-api serves is served same-origin from one of its [[static]] mounts, so the API base URL is simply the page’s own origin and no CORS setup exists at all.

Those mounts are an array of tables — the doubled brackets are load-bearing. The older single [static] table is refused at load rather than ignored, and each route must be unique across the entries. See the [[static]] list for the full rules and the refusals they produce.

The webmail app

The webmail app is the one client an operator serves outright: there is no store listing and nothing for the user to install beyond the browser’s own PWA prompt.

Reading mail rides the /v1/mail surface, which needs only the shared [store]. Compose (POST /v1/mail/send) routes recipients the way sithbitd’s submission port does, so its reach follows the API’s config:

  • No extra config — bare base58 wallet addresses deliver locally; anything else answers 503 ( alias resolution needs the chain).
  • [chain] — aliases and user@your-domain addresses resolve through the gateway, with the frombox postage precheck applied before the message is accepted.
  • [mail] (local_domains, [mail.dkim]) — mirrors sithbitd’s submission settings: which domains are yours (everything else is relayed) and the DKIM keys relayed mail is signed with.

Build the bundle and mount it:

cd webclients/webmail
./build.sh    # wasm-pack build + stages shared/ into staging/

staging/ is the deployable bundle. Point account-api at it:

[[static]]
route = "/mail"
root = "webclients/webmail/staging"

then open http://127.0.0.1:8180/mail/index.html (or your deployment’s origin).

The app does not require you at all, though: clearing the API URL in the connection settings switches it into trustless mode, wallet-only and server-free. If you offer a pin service to those users, read the pin lifecycle caveat first — client-made pins are outside every mail server’s pin lifecycle.

The name marketplace page

The marketplace panes ship inside the other clients, but the standalone page is served the same way webmail is:

cd webclients/marketplace
./build.sh    # wasm-pack build + stages shared/ into staging/

staging/ is the deployable bundle, served same-origin from account-api exactly like the webmail app — no CORS, the API base URL is the page’s own origin:

[[static]]
route = "/marketplace"
root = "webclients/marketplace/staging"

Give this entry a route no other entry uses.

CSP note. The standalone page bridges the wire transaction to the format the external wallet signs with the vendored codec-only @solana/kit bundle (shared/lib/kit-codec.esm.js) — served from the same origin as the page, so account-api’s script-src needs no extra origins.

[chain] is required here rather than optional: all three routes the pane uses answer 503 without it. The Sold tab is additionally only as complete and current as your alias/marketplace indexer — disable the indexer and the tab is empty.

The Chrome extension

The Chrome extension is installed by the user, not served by you; what you provide is the account API it points at.

Its three-pane mail reader rides the account API’s /v1/mail surface, so it appears whenever an account API is configured. The trustless on-chain inbox talks to no server at all — it needs only a Solana RPC endpoint and an IPFS gateway (both under the extension’s Connection settings), so it keeps working with the mail server down, and it is the only reader shown when the account API url is left blank.

The extension ships with host permissions for http://127.0.0.1 / http://localhost by default and https://* as an optional grant, so a user pointing it at your remote API is prompted for that origin.

The Outlook add-in

Office add-ins are https-hosted web pages, so you serve the built bundle from one of account-api’s [[static]] mounts — the same origin as the API, which is why the add-in needs no CORS setup. Office requires https: terminate TLS with a reverse proxy, or use account-api’s [tls] section (see the configuration reference, including the dev-certificate recipe).

[[static]]
route = "/addin"                      # the default route, spelled out
root = "webclients/outlook/staging"

route defaults to /addin, so an entry naming only root serves the same URL.

The bundle itself comes out of the same ./build.sh the user runs to get a sideloadable package — see Building and sideloading on the client page for that command and the sideload paths.

The Thunderbird extension

The Thunderbird extension talks only to account-api and there is nothing for you to host: MailExtensions need no signing, so the .xpi a user builds or downloads installs permanently. Your side is the [chain] section above, and — because the extension ships with host permissions for http://localhost / http://127.0.0.1 only — an origin your users will be prompted to grant when they point it at your remote API.