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

The webmail app

A plain-browser webmail client: the folder rail, a newest-first message list with per-folder search, a sandboxed reader, and a compose pane — plus the same wallet login and settings panes the Thunderbird and Outlook clients carry, because all three hosts run the same shared core (webclients/shared/). Everything that must be signed is signed in the page by a WebAssembly module compiled from this workspace’s own crates; the server only relays already-signed transactions and never holds your key.

Navigation is hash-only — #/mail (the three-pane mail view) and #/settings (the shared dashboard panes). There is no build framework and no CDN: the bundle is static files served by account-api itself.

The server is optional, though: clearing the API URL in the connection settings switches the app into trustless mode — wallet-only unlock, on-chain mail read and sent with nothing but a Solana RPC node and an IPFS gateway/pin daemon.

What the operator must run

The app is served same-origin from account-api’s [static] section, so the API base URL is simply the page’s own origin and no CORS setup exists at all. 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.

As with the other clients, the aliases/balances/keys settings panes also need [chain]; login, mail reading, and the account settings work without it.

Building and serving

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).

Install as an app (PWA)

The webmail bundle is an installable progressive web app. Once it is served over its origin, the browser offers its native install prompt on the desktop (Chrome/Edge’s address-bar install button) and Add to Home Screen on mobile, giving you a standalone SithBit Mail window with its own icon and no browser chrome — tinted the brand teal.

A service worker precaches the static shell (HTML, CSS, JS, and the wasm module), so the app loads offline — the login, unlock, and dashboard frames paint with no network. Your mail itself stays live: the service worker never caches the account API (/v1/*), so every message, folder count, and send always hits the server. Open the app with the server down and you get the shell; you reach your mail again the moment it is back.

First run and onboarding

A brand-new visitor — no wallet stored in this browser yet — lands directly in the shared five-step onboarding wizard: create or import a wallet (the create branch shows your secret key once, with the save it — it is the only copy gate), claim an optional handle, set your default postage, review, and mint your mailbox on-chain. Until that finishes the app hides its mail and settings views and shows only the wizard. Every transaction is built and signed in the page by the wasm module; the server only relays it.

The app decides which of four views to paint from the session it probes on load:

  • Onboarding wizard — no wallet stored yet, or an unlocked wallet that owns no mailbox (a returning user finishing setup).
  • Unlock — a wallet is stored but locked this run; the app asks for its passphrase (the key is encrypted with that passphrase before it lands in the browser’s localStorage, and is asked for again each time you open the app).
  • Dashboard — a fully set-up account: unlocked and mailbox-registered — the normal #/mail and #/settings views below.
  • (a brief loading view while it probes.)

Because the webmail bundle is served same-origin from account-api, it needs no endpoint configuration at all: the API base URL is simply the page’s own origin. The onboarding create relays through that same API, so its on-chain reach follows the API’s [chain] config exactly as the compose path does above.

Using it

The #/mail view is three panes. The folder rail lists your folders with unseen counts; the middle column stacks per-folder search over the message list (keyset-paged, newest first); the reader marks messages read on open, toggles text/HTML/raw views (HTML render in a fully sandboxed iframe that blocks scripts and remote loads), downloads attachments, flags, moves, and deletes. A ✓ Verified trust mark appears beside the From line when the sender holds an on-chain verified-sender attestation from its domain — here it checks the sender the (ingress-authenticated) From header names, while the trustless viewer’s badge binds the program-verified on-chain envelope signer. No mark simply means “not attested”; it is never a warning. Compose floats bottom-right: To/Cc take comma-separated addresses — aliases, user@domain, or bare wallet addresses — and the reader’s Reply action opens it prefilled (sender as To, Re: subject, threading header). A successful send files an already-read copy in Sent and refreshes the folder counts; recipient problems (unknown alias, no stamps on your frombox) surface on the pane with your draft intact.

The #/settings view is the shared dashboard: mail password — typed, or derived from a wallet signature with the pane’s Derive mail password button (available while the wallet is unlocked; see The mail password) — timezone, do-not-disturb, aliases, balances, delegated encryption-key management, domain trading (list a domain you hold for sale on the open marketplace, or buy a listed one by name and authority), settling reply bounties (claim one on a message you received and replied to, or refund one you placed that expired unclaimed), pinning leases (escrow a refundable deposit asking operators to keep a message’s pinned body past the default retention — create a lease by the message’s CID and id, check a CID for your wallet’s lease, or close it anytime to reclaim the deposit), claiming and configuring your mailbox (the handle, sending domain, default postage, and on-chain-only opt-out), and closing the mailbox — a two-step, 7-day timelocked flow, so the request only starts a clock (the mailbox stays open and receiving, nothing is refunded), a cancel is available throughout, and both rents return only when you come back after the wait and finish the close. All of it behaves exactly as documented for the Thunderbird extension. The trustless viewer’s Lease this message button (beside Reply, shown once a body has rendered) prefills the pinning-leases create form with the open message’s CID and id and routes the shell straight here — the URL hash becomes #/settings, so the browser’s back button returns to the mail view. You still review the deposit and submit, and entering a CID by hand works as before.

The compose loop is exercised headlessly by the env-gated live suite webclients/webmail/test/e2e-webmail.test.js — the bundle served from a real account-api, a real send to a throwaway wallet, and the message read back from the recipient’s INBOX (the runbook lives in webclients/README.md).