The Thunderbird extension
A MailExtension for self-service on a SithBit account: wallet login, mail password, timezone, do-not-disturb schedules, aliases, balances, and delegated encryption-key management. Everything that must be signed is signed inside the extension by a WebAssembly module compiled from this workspace’s own crates — the server only relays already-signed transactions and never holds your key.
Requires Thunderbird 140 or later.
What the operator must run
The extension talks only to account-api.
For the aliases, balances, and encryption-key panes, the API needs its
[chain] section configured (a mail-grpc gateway plus a Solana RPC
endpoint — see the
configuration reference);
without it those panes report the surface as unavailable while login and
the settings panes keep working.
Installing from the store
Once the extension is published, the one-click route is
addons.thunderbird.net (ATN): search
for _todo_store_listing_name_ in the Add-ons Manager (gear menu →
Add-ons and Themes) and install it directly from the search results,
or open _todo_store_listing_url_ in a browser and install from the
listing page. Store installs update automatically.
Note: the extension is not yet published to addons.thunderbird.net. The
_todo_store_listing_name_and_todo_store_listing_url_placeholders resolve when the store listing goes live; until then, install via Building and installing below.
Thunderbird MailExtensions need no signing, so self-distributing the
.xpi — the build path below — is a fully supported permanent
channel, not a workaround. ATN listings are public-only (no unlisted or
private tier), which makes the store listing an optional later step
rather than a pre-release necessity.
Building and installing
cd webclients/thunderbird
./build.sh # wasm-pack build + stages shared/ + zips the xpi
Install sithbit-thunderbird.xpi via Thunderbird’s Add-ons Manager
(gear menu → Install Add-on From File). Thunderbird accepts
self-built, unsigned xpi files permanently — no store listing is
needed. For development, point Load Temporary Add-on (Tools →
Developer Tools → Debug Add-ons) at thunderbird/staging/manifest.json.
The extension ships with host permissions for http://localhost /
http://127.0.0.1 only. Pointing it at a remote API (extension
options) prompts for that origin’s permission when you save.
First run and onboarding
Opening the SithBit dashboard for the first time — with no wallet stored in this Thunderbird profile yet — drops you straight into the shared five-step onboarding wizard rather than the wallet manager. It creates or imports a wallet (the create branch reveals your secret key once, with the save it — it is the only copy gate), claims an optional handle, sets your default postage, and mints your mailbox on-chain — the mailbox and, if you claimed a handle, its alias ride one signed transaction, built and signed in the extension’s wasm module. Until it finishes, the dashboard shows only the wizard.
The dashboard routes to one of four views from the session it probes on open:
- Onboarding wizard — no wallet stored yet, or an unlocked wallet with no mailbox (a returning user finishing setup).
- Unlock — a stored wallet that is locked this run; the shared wallet-list pane below owns the passphrase prompt.
- Dashboard — unlocked and mailbox-registered: the normal panes.
- (a brief loading view while it probes.)
The onboarding relay and the trustless endpoints all come from the
extension’s options page, not from any hosted config: the
account-API origin (which also prompts for its host permission when you
save a remote one), the Solana RPC (config.rpcUrl), the IPFS
gateway (config.gatewayUrl), and — for the
on-chain compose — the IPFS pin
service (config.ipfsPinUrl plus its bearer token), each defaulting to
the local dev stack. See
Trustless (server-down) viewing
for the read-path endpoint defaults.
Wallets and their passphrases
The SithBit button in the spaces toolbar opens a wallet manager, not a single-account gate — you can import more than one Solana keypair file (a JSON array of 64 numbers) into the same Thunderbird profile, each under its own passphrase, and unlock several of them at once. Every imported wallet is encrypted with its passphrase (PBKDF2 + AES-GCM) before it is stored in the Thunderbird profile; decrypted keys live only in memory and are gone when Thunderbird exits, so each restart asks for the passphrase again for whichever wallets you want unlocked this session.
Unlocking a wallet is a login challenge: the API issues a nonce, the extension signs it with that wallet, and a day-long session token comes back. No password ever exists for login — the wallet is the account. One unlocked wallet at a time is active; the settings, balances, aliases, keys, and trustless-viewer panes all act on whichever wallet is active, and switching between already-unlocked wallets needs no passphrase. This lets one profile manage several SithBit addresses (e.g. a personal one and a work/domain one), but each address still needs its own separate account added through Thunderbird’s native Add Mail Account wizard — Thunderbird’s stable extension API has no way to create that account programmatically (see The mail password below for what to enter there).
Panes
- Balances — wallet SOL, on-chain mailbox message count, and a per-sender stamp lookup (stamp balances are held per sender, so there is no single “total stamps” number).
- Aliases — every alias pointing at your wallet. Registration and transfer stay in the CLI.
- Domains — list a domain you already hold for sale on the open marketplace, or buy one that’s listed by name and current authority. Reassigning a domain’s authority is an operator action and isn’t offered here. Needs the unlocked wallet.
- Reply bounties — settle reply bounties on-chain: claim one on a message you received and replied to (giving the bountied message’s id, the original sender, and your reply’s id), or refund a bounty you placed that went unclaimed past its deadline.
- Pinning leases — escrow a refundable deposit asking operators to keep a message’s pinned body past the default retention (pinning leases). Create a lease by the message’s CID and id — the recipient defaults to your own mailbox, the deposit prefills to the protocol minimum, and a one-time creation fee applies — then check whether your wallet holds a lease on a CID, or close it anytime to reclaim the deposit. Closing works even after the message itself has settled: the lease is addressed by the CID, not the message. The trustless reader further down this dashboard carries a Lease this message button beside Reply (shown once a body has rendered) that prefills this pane’s create fields with the open message’s CID and id — prefill only, so the deposit is still yours to review and submit, and typing a CID by hand works as before.
- Encryption key — publish, rotate, or close a
delegated X25519 key. The transaction is
built and signed in the extension’s wasm module and relayed through
the API. Export the secret when it is shown — it appears exactly
once, in the same JSON format
sithbit mail get -xreads; mail sealed to an old key still needs that old key’s secret, so keep every export. - Settings — mail-client credentials for the active wallet (see The mail password below) and its IANA timezone. The pane’s Copy mail password button derives the wallet-signature credential for you, so you never have to run the CLI.
- Do not disturb — weekly or date-range windows during which senders are asked to retry later, evaluated in your timezone.
- Mailbox — where you claim your on-chain mailbox and set its registration config: an optional handle (claimed as an alias in the same transaction), the sending domain (blank uses sithbit.com), the default postage — the per-stamp price unknown senders pay to reach you — and whether to store bodies on-chain only (opting out of IPFS). Once the mailbox is registered the pane shows the received-message count and sends you to the Balances pane to adjust per-sender prices.
- Close mailbox — request, cancel, or finish closing your mailbox. Closing is not instant: the request opens a fixed 7-day wait during which the mailbox stays open, keeps receiving mail, and refunds nothing. The pane shows the remaining time and offers Cancel throughout — cancelling leaves the mailbox exactly as it was — and only once the clock has run does the finalize button appear, closing the mailbox and returning both its rent and the transient request record’s to your wallet. The wait is deliberate: an instant refund made discarding a burned sending identity free, so the cost of leaving is time on a rare action rather than money. It does not apply to the Encryption key close above, which stays instant on purpose.
Zero-config setup (autoconfig)
If the operator runs the domain-sithbit
service, Thunderbird can fill in all the IMAP/ POP/SMTP connection settings
for you — no plugin, no hand-typed hostnames. In the native Account
Setup wizard (File → New → Existing Mail Account), enter your address
as <wallet-base58>@<domain> and Thunderbird fetches the server settings
from the domain’s Mozilla autoconfig document and auto-fills them.
Two things the wizard does not do, by design:
- It does not create the account. Autoconfig fills in connection
settings only; native account auto-provisioning is deliberately not
offered. Your mailbox must already exist on-chain (
sithbit mailbox create) before you connect. - It does not set your password. The username is auto-filled as your
wallet address (the base58 public key — the
@domainis dropped), but you still supply a mail password. Get it either from the self-serve enrollment page the operator hosts (/addin/enroll.htmlon the account API) or offline from the CLI — see The mail password just below. Then finish the wizard.
If the operator hasn’t enabled autoconfig, enter the same settings by hand — the username and password rules are identical, and the mail-server coordinates are whatever the operator advertises (standard IMAPS 993 / POP3S 995 / submission 587 by default).
The mail password
Your IMAP/POP/SMTP client authenticates as your wallet with no separate stored password: the “mail password” is a signature your wallet produces over a fixed challenge, which the servers verify against your wallet address. Nothing is stored server-side — the signature is self-proving — so you never “set” a password on the account (see the account API, where a stored password is now optional).
Two ways to get the credential:
-
In the extension — the Settings pane’s Copy mail password button derives it in the wasm module and copies it to your clipboard.
-
On the command line — run:
sithbit mailbox credentials -k ~/.config/solana/id.jsonwhich prints the pair offline (no RPC, no on-chain write).
Enter it in your mail client as:
- Username — your wallet address (the base58 public key), exactly as the CLI prints it.
- Password — the derived base58 signature.
- Mechanism — SASL PLAIN over TLS (STARTTLS or implicit TLS). The signature is bearer-equivalent for the connection, so always use TLS.
This is the tested path for reading mail — POP3 and IMAP retrieval
authenticate with the wallet signature end to end. Wallet-authenticated
submission (sending) authenticates the same way, and the envelope
sender you may present is fixed: exactly your own wallet address, at
a domain the submission server is authoritative for — and, when that
server is connected to the chain, one your wallet also owns on-chain
(the domain’s recorded authority), not merely any domain the server
serves. Set the account’s identity to
<your-wallet-base58>@<the operator's domain> — the same address you
typed as the username, with the operator’s mail domain — and sending
works. Anything else is refused 553 5.7.1: another wallet’s address,
your address at a domain that server does not serve (or, on a
chain-connected server, that your wallet does not own on-chain), or a
case-variant of your own base58 (base58 is case-sensitive, so Alice
and alice are different keys and the match is exact). Copy the address
from the CLI rather than retyping it. Regenerating the credential is
free and offline — the wallet key never leaves your machine.
If your operator runs a chain-less dev stack and sends are refused
553 5.7.1 even though the address looks right, the server’s
local_domains is the likely culprit — see Wallet submission
envelopes.
Client-certificate login (SASL EXTERNAL)
The passwordless alternative to the mail password above: instead of a
signature you paste as a password, you hand your mail client a TLS
client certificate minted from your wallet, and the server logs you in
over the TLS handshake itself — no credential typed, nothing stored on
either side. The server must have client_cert_auth turned on for the
listener (see the
configuration reference);
where it does, this and the mail password both work, so pick whichever
your client handles best.
-
Mint the certificate — offline, no RPC:
sithbit mailbox create-cert -k ~/.config/solana/id.json --out sithbitThis writes
sithbit.p12— the combined, password-less PKCS#12 bundle, the one file Thunderbird imports — alongsidesithbit.crt(the certificate) andsithbit.key(its PKCS#8 private key) as a PEM pair for other apps. Omit--outto print just the two PEM blocks to stdout instead. The certificate is a self-signed Ed25519 leaf whose public key is your wallet address, so it needs no CA and never expires into a renewal chore — regenerate it any time from the same keypair. The.p12and.keyfiles embed your wallet secret; guard them like the wallet itself. -
Install it in Thunderbird. Import
sithbit.p12in one step under Settings → Privacy & Security → Manage Certificates → Your Certificates → Import — the bundle is password-less, so leave the password prompt blank. Then, on each of the account’s SMTP (outgoing), IMAP, and POP server entries, set the connection security to SSL/TLS, the authentication method to Encrypted certificate, and select this certificate. -
Leave the password blank. With EXTERNAL there is no password to enter — the certificate is the whole credential. Use your wallet address as the username exactly as before.
No CLI at hand? The extension mints the identical files: the
dashboard’s signed-in view carries a Certificate sign-in section
directly below the panes, whose Download client certificate button
derives everything in the wasm module — no server involved — and saves
three files named after your wallet address: the combined
<pubkey>.p12 first (the one Thunderbird imports in one step, exactly
as in step 2), then the <pubkey>.crt / <pubkey>.key PEM pair for
other apps — byte-for-byte what sithbit mailbox create-cert writes.
Importing stays manual exactly as in step 2 — a WebExtension has no
way into Thunderbird’s certificate store, so the download is the whole
affordance. The .p12 and .key files embed your wallet secret;
guard them like the wallet itself. The button needs the active wallet unlocked in the
extension’s wasm module: while every wallet is locked it is disabled,
and an external signer (a Phantom or Ledger wallet) reads as locked
too — it holds no local seed to derive from, so external-wallet users
stay on the CLI path above.
Because the certificate carries no secret beyond your wallet key (which never leaves your machine), you can regenerate and reinstall it freely — the derivation is fully deterministic per wallet, so re-downloading on any device yields the identical certificate, and a fresh download never invalidates one already imported.
Trustless (server-down) viewing
The dashboard also carries a Read mail trustlessly pane — a read path that needs nothing but a Solana RPC endpoint, any IPFS gateway, and your unlocked wallet. It works with the SithBit mail server (account-api) completely down: enter a mailbox message id and the extension resolves that message’s on-chain account for its sealed-body CID, fetches the sealed bytes from the gateway, unseals them in the wasm module with your wallet, and renders the MIME. HTML bodies render in a fully sandboxed frame that blocks scripts and remote loads, exactly like every other reader here.
Two endpoints drive it, both set in the extension options and
defaulting to the dev stack: the Solana RPC (config.rpcUrl, default the
local surfpool at http://127.0.0.1:8899) and the IPFS gateway
(config.gatewayUrl, default http://127.0.0.1:8183 — e.g. a
sithbit-gateway). The pane relies on the
messagesRead permission, already declared in the manifest.
Two caveats:
- A wallet-only read only works if the body was sealed to your
wallet. A recipient who published a
delegated encryption key had their mail sealed
to that key instead, so the trustless read needs the delegated
secret, not the wallet — the same secret
sithbit mail get -xreads. - The sealed IPFS copy is only fetchable while it stays pinned. Under the
default
[spooler.settle]auto-settle worker, a message settled past its window is unpinned (keep_pin = false), so a trustless read of an old, settled message depends on the operator having setkeep_pin = true.
Trustless reply and compose
Reading is no longer the pane’s whole surface: the trustless reader’s
header carries a Reply on-chain button, and the dashboard mounts
the same floating on-chain compose card as
trustless webmail — a
Compose on-chain button opens it blank; Reply seeds it with the
decrypted sender and the parent message’s account address. The card is
always available (the dashboard has no trustless mode to switch into)
and follows the webmail card’s lifecycle exactly: resolve the recipient
and their published key on-chain, seal in the extension’s wasm module,
pin the sealed bytes to your configured IPFS pin service, then sign and
submit the SendMail transaction.
A reply sent this way is an on-chain send, not a Thunderbird compose: it never opens a compose window or touches the account’s outgoing SMTP server, and the operator’s SMTP/IMAP/spooler pipeline plays no part in delivery — the reply lands as an on-chain message account even while those servers are down. Chain reads, sealing, and pinning go straight to your configured endpoints; the signed transaction is relayed through the account API like every other on-chain action here, and as always the relay cannot alter what you sign (see Security notes).
The card carries webmail’s three affordances, documented in full there rather than restated here:
- The reply chip — the draft threads to the parent message with the
same privacy-preserving linkage
--reply-tomakes (only its blake3 hash reaches the chain); Clear drops the link and keeps the draft. See Replying, and attaching a bounty. - Attach a reply bounty — an amount in SOL (blank = plain send) and
a claim window in days escrow SOL on the message exactly like
--bounty/--bounty-window. - Inline prepay — no frombox toward the recipient yet? The card holds the draft, quotes the postage (live per-stamp protocol fee included), and Prepay & send buys the stamps then re-sends the held draft once the purchase confirms — the same prepayment rule and behavior as webmail’s card. The Balances pane remains the standing place to buy stamps outside a compose.
Sending needs one endpoint reading does not: the options page gains an
IPFS pin service URL (config.ipfsPinUrl, default
http://127.0.0.1:8182 — a
sithbit-ipfsd) plus its optional bearer
token (config.ipfsPinToken, default empty), where outbound sealed
bodies are pinned. As with a remote API origin, saving a non-loopback
pin URL prompts for that origin’s host permission on the same Save
click — the extension ships with loopback-only host permissions.
Operators offering that pin surface should read
the pin lifecycle caveat:
a client-made pin sits outside any mail server’s pin lifecycle.
Security notes
- The wallet secret at rest is exactly as strong as your passphrase.
- The session token authorizes account changes (password, timezone, DND) for up to 24 hours; on-chain actions additionally require the unlocked wallet, which never survives a restart.
- The API relay cannot alter what you sign: transactions are built from the workspace’s own instruction encoders compiled to wasm, and a parity test pins them byte-for-byte to the CLI’s.