The Outlook add-in
An Office.js taskpane add-in for self-service on a SithBit account:
wallet login, mail password, timezone, do-not-disturb schedules,
aliases, balances, and delegated encryption-key management — the same
panes as the Thunderbird extension, because both hosts run the same
shared core (webclients/shared/). Everything that must be signed is
signed inside the pane by a WebAssembly module compiled from this
workspace’s own crates; the server only relays already-signed
transactions and never holds your key.
Runs in new Outlook on Windows and Outlook on the web via the unified
JSON manifest. On classic Windows desktop Outlook the add-in
installs via its XML manifest instead (build.sh renders it to
package/manifest.xml), and Lockbox sealing is not available at
send time there: the classic send runtime gives the hook no module
loading and no network access, so the send hook states that sealing is
unavailable and lets your message send exactly as you typed it. Not
Outlook for Mac — the unified JSON manifest doesn’t run there; the
same legacy XML manifest route is the recorded fallback.
What you need from your operator
An Office add-in is an https-hosted web page, so the pane itself is served by your operator’s account-api — you will be given that origin, and the manifest you sideload below points at it. As with Thunderbird, the aliases, balances, and encryption-key panes need that API’s chain surface configured; login and the settings panes work without it.
Operators: what to serve and how to mount it is Serving the browser clients.
Installing from the store
Once the add-in is published, install it from
Microsoft Marketplace (formerly
Microsoft AppSource): in
Outlook open Apps → Get Add-ins and search for
_todo_store_listing_name_, or open _todo_store_listing_url_ in a
browser and choose Get it now. Store installs update automatically.
Note: the add-in is not yet published to Microsoft Marketplace. The
_todo_store_listing_name_and_todo_store_listing_url_placeholders resolve when the listing goes live; until then, install via Building and sideloading below.
Microsoft Marketplace listings are public-only and Microsoft-validated
— there is no unlisted tier. The supported private paths are sideloading (the
atk flow below) and Microsoft 365 admin center → Integrated Apps →
Upload custom app, which deploys the add-in privately to a whole
tenant — the de-facto org-wide pre-release deployment.
Building and sideloading
Until the marketplace listing is live this is how you install the add-in — building the package yourself, from this workspace:
cd webclients/outlook
./build.sh # wasm-pack build + stages shared/ into staging/ + packages sithbit-outlook.zip
Sideload sithbit-outlook.zip with the Agents Toolkit CLI
(atk auth login m365, then atk install --file-path sithbit-outlook.zip) or via Teams → Apps → Upload a custom app. The
Outlook-web “Add-Ins” upload dialog accepts only XML manifests — use
one of the two paths above. On classic Windows desktop Outlook,
sideload the rendered package/manifest.xml instead (aka.ms/olksideload
→ My add-ins → Add a custom add-in → Add from file) — that XML manifest
is what carries the add-in there. The full dev runbook, including the
localhost-https trust step, lives in webclients/outlook/README.md.
First run and onboarding
The first time you open the pane in a browser profile with no wallet
stored yet, it opens on the shared five-step
onboarding wizard
instead of the wallet manager. It creates or imports a wallet (the create
branch shows 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 — mailbox plus, if claimed, its alias in one transaction, signed
in the pane’s wasm module and relayed through /v1/chain. Until it
finishes, the pane shows only the wizard.
The pane 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 add-in needs no endpoint configuration for onboarding: because the bundle is served same-origin from account-api, it infers the API base URL from the page’s own origin, and the onboarding create relays through that same API. Only the optional trustless fallback and its on-chain compose read endpoints of their own (RPC, IPFS gateway, and pin service, from localStorage).
Using it
Select any message and open Apps → SithBit Account (the pane is
pinnable, so it stays open as you move around). The pane opens on a
wallet manager, not a single-account gate: paste a Solana keypair file
and choose a passphrase to import it — the key is encrypted with that
passphrase before it is stored in the browser’s storage — and repeat
for as many SithBit addresses as you want available in this browser
profile. Each is unlocked with its own passphrase (needed again each
time the taskpane opens), several can stay unlocked at once, and
switching which one is active needs no passphrase. The settings,
DND, aliases, balances, mail view, and delegated-key panes all act on
the active wallet, each on-chain action signed client-side in wasm and
relayed through /v1/chain. As with Thunderbird, each address still
needs its own native Outlook mail account — Office.js add-ins run
inside an already-configured mailbox and have no API to provision one.
Every flow above is exercised headlessly by the env-gated live suite
webclients/outlook/test/e2e-outlook.test.js — the bundle served from
a real account-api, login and settings through the real storage
adapters, and the key lifecycle confirmed on-chain.
Panes
The taskpane mounts the same shared dashboard the other clients do, so the pane set — and its five-tab strip (Wallet / Mailbox / Names / Services / Sign-in, with a half-second hover note on each tab) — is identical to Thunderbird’s. The taskpane’s own Mail / Settings / Marketplace buttons pick the view; the panes are the Settings view.
- Balances — wallet SOL, the on-chain mailbox message count, and a per-sender stamp lookup (stamps are held per sender, so there is no single total). Outbound, Quote shows the live per-stamp price toward a recipient and pins the purchase to it; Reclaim unspent takes idle postage back out of a frombox of your own — see the webmail write-up, which describes the same shared pane.
- Aliases — the aliases pointing at the active wallet. Registering and transferring them stays in the CLI.
- Domains — list a domain you hold for sale on the open marketplace, or buy a listed one by name and current authority. Reassigning a domain’s authority is an operator action, not offered here.
- Reply bounties — settle reply bounties on-chain: claim one on a message you received and replied to (bountied message id, original sender, your reply’s id), or refund one 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 one by the message’s CID and id (deposit prefills to the protocol minimum; a one-time fee applies), check a CID for your wallet’s lease, or close it anytime to reclaim the deposit — even after the message has settled. The trustless fallback reader’s Lease this message button (beside Reply, shown once a body has rendered) prefills this pane’s create fields with the open message’s CID and id, switches the taskpane to the settings view (a plain view switch — the taskpane keeps no history) and opens the panes’ Services tab; you still review the deposit and submit, and manual CID entry works as before.
- Encryption key — publish, rotate, or close a
delegated X25519 key,
signed in wasm and relayed through
/v1/chain. Export the secret while it is on screen — it is shown once, and mail sealed to an old key still needs that key. - Settings — the derived mail password for the active wallet (see The mail password below) and its IANA timezone.
- Do not disturb — weekly or date-range windows in which senders are asked to retry later, evaluated in your timezone.
- Mailbox — 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 unknown senders pay per stamp, and an on-chain-only (no IPFS) option. Once registered the pane shows the message count and points per-sender price changes to the Balances pane.
- Close mailbox — request, cancel, or finish closing the mailbox. This one is not instant: the request starts a fixed 7-day wait and refunds nothing, the mailbox stays open and keeps receiving mail meanwhile, and the pane offers Cancel for the whole period. Only after the clock runs out does finalizing become available, closing the mailbox and returning its rent and the transient request record’s — both to your wallet for a mailbox you created yourself, while a sponsored mailbox sends the mailbox rent back to the sponsor that funded it and leaves you the request record’s. The pane says which happened. The wait exists because an instant refund made discarding a burned sending identity free; it costs an honest owner time on a rare action, not money. The Encryption key close above is unaffected and stays instant.
Zero-config setup (autodiscover)
If the operator runs the domain-sithbit
service, Outlook’s native Add Account flow can fill in the
IMAP/ POP/SMTP connection settings for you — no add-in, no hand-typed
servers. Type your address as <wallet-base58>@<domain>; Outlook POSTs it
to the domain’s autodiscover endpoint and auto-fills the servers, ports,
and TLS modes from the operator’s advertised coordinates.
Two limits, by design:
- It does not create the account. Autodiscover 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 login name is auto-filled as your
wallet address (the base58 public key — the
@domainis not leaked), but you still supply a mail password. Get it from the operator’s self-serve enrollment page (/addin/enroll.htmlon the account API) or offline from the CLI — see The mail password just below.
Where the operator hasn’t enabled autodiscover, enter the same settings by hand; the username and password rules are identical.
The mail password
Like the Thunderbird extension, your IMAP/POP/SMTP client authenticates as your wallet with no separate stored password. The “mail password” is a wallet signature over a fixed challenge that the servers verify against your wallet address; nothing is stored server-side, so a password on the account is now optional (see the account API).
Get the credential for the active wallet from the Settings pane’s Copy mail password button, or offline from the CLI:
sithbit mailbox credentials --keypair ~/.config/solana/id.json
The same pane’s Rotate the wallet mail password… button retires it when
you need to: a two-step control that bumps your account’s auth epoch, so
every copy of the old password — this add-in’s included, until you copy the
new one — stops authenticating at once, and the CLI derives the replacement
with sithbit mailbox credentials --epoch <N>. It rotates the wallet-derived
password alone; a stored mail password, your session, and a
client-certificate login are
unaffected. See
Rotating the wallet mail password.
Rotation is
step-up gated:
the server refuses it with 428 Precondition Required until the request
carries a wallet signature made seconds ago, so a live session alone cannot
change your mail credentials. The pane answers that for you — it fetches the
challenge and has your wallet sign it, which is the approval prompt a Phantom
or Ledger wallet raises — and what surfaces in the add-in is only the case
where no signature could be produced (locked or disconnected wallet, declined
prompt, stale challenge): “This change needs a fresh wallet signature. Make
sure your wallet is unlocked (or reconnected) and try again.” Do exactly
that — unlock or reconnect, arm the control again, approve the prompt.
Waiting achieves nothing, because every attempt spends its own challenge and
needs its own signature; and a refused rotation leaves the epoch and the
password this add-in already holds untouched. Offline, the same signer is
sithbit mailbox sign-text,
signing the challenge string with --keypair.
Enter it in your mail client as username = your wallet address (base58
public key), password = the derived base58 signature, mechanism = SASL
PLAIN over TLS (the signature is bearer-equivalent, so always use TLS).
This is the tested path for reading mail (POP3/IMAP retrieval);
wallet-authenticated submission authenticates identically, and its
envelope sender is fixed to exactly your own wallet address at a domain
the submission server serves — 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 happens to serve. Set the
Outlook account’s email address to <wallet-base58>@<the operator's domain>,
the same string the add-in POSTs to autodiscover. 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), and a case-variant of your
own base58 (base58 is case-sensitive, so the match is exact) are each refused
553 5.7.1; the
Thunderbird page and the configuration
reference
carry the full rule and the chain-less-dev-stack gotcha. Regenerating the
credential is free and offline.
Client-certificate login (SASL EXTERNAL)
The passwordless alternative to the mail password above, exactly as on
Thunderbird:
instead of pasting a signature, you hand your mail client a TLS client
certificate minted from your wallet, and the server authenticates you
over the TLS handshake — no password entered, nothing stored server-side.
It requires client_cert_auth on the listener (see the
configuration reference).
-
Mint the certificate — offline, no RPC:
sithbit mailbox create-cert --keypair ~/.config/solana/id.json --out sithbitThis writes
sithbit.p12— the combined, password-less PKCS#12 bundle meant for your certificate store’s import dialog — alongsidesithbit.crtand its PKCS#8 keysithbit.keyas a PEM pair for other apps (omit--outto print just the PEM blocks to stdout). The certmgr / Keychain import in step 2 hasn’t been walked end-to-end here yet; Thunderbird’s own certificate store is known to refuse this bundle — see the known issue. The certificate is a self-signed Ed25519 leaf whose public key is your wallet address — no CA, no expiry to manage. The.p12and.keyembed your wallet secret; guard them like the wallet itself. -
Install it. The Office.js taskpane above manages only the account settings, not the mailbox’s IMAP/POP/SMTP transport, so a client certificate is configured in the host mail app, not the add-in. In classic Outlook that is File → Options → Trust Center → Email Security → import your certificate — pick
sithbit.p12and leave the password prompt blank (the bundle is password-less) — then attach it on the account’s outgoing/incoming server security settings; on new/web Outlook the certificate is selected by the OS/browser certificate store when the server requests one. Set each of the account’s SMTP, IMAP, and POP entries to SSL/TLS and select this certificate as the client certificate. -
Leave the password blank — with EXTERNAL the certificate is the whole credential; keep your wallet address as the username.
The add-in mints the same files without the CLI: the taskpane’s
settings view carries a Certificate sign-in section on the panes’
Sign-in tab, whose Download client certificate button derives
everything in the wasm module and saves three files named after your
wallet address: the combined <pubkey>.p12 first, then the
<pubkey>.crt / <pubkey>.key PEM pair for other apps —
byte-for-byte what sithbit mailbox create-cert writes. Importing
stays manual as in step 2 — an Office add-in cannot touch the OS
certificate store — so bring the .p12 into certmgr on Windows or
Keychain Access on macOS (or your mail app’s own certificate settings)
— the same import caveats as step 1 apply. The .p12 and .key
files embed your wallet secret;
guard them like the wallet itself. The button needs a
wallet unlocked in the add-in’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.
Regenerate and reinstall it freely; it carries no secret beyond your wallet key, which never leaves your machine — the derivation is fully deterministic per wallet, so re-downloading on any device yields the identical certificate.
Trustless (server-down) viewing
The taskpane wires two read paths. The primary one is the server
mail view over account-api’s /v1/mail surface (the same three-pane
reader the webmail app shows). Behind it sits a trustless fallback
that reads a message straight from a
Solana RPC endpoint and an IPFS
gateway with the mail server down: it resolves the message’s
on-chain account for its sealed-body CID, fetches the sealed bytes from
the gateway, unseals them in wasm with your unlocked wallet, and renders
the MIME — HTML in a fully sandboxed frame that blocks scripts and
remote loads. The fallback is a no-op until the wallet is unlocked.
Its two endpoints come from localStorage and default 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). Paste a bare URL into
either key to point the fallback at a remote endpoint.
The same two caveats as the Thunderbird extension apply:
- A wallet-only read only works if the body was sealed to your wallet; a recipient who published a delegated encryption key needs that delegated secret instead.
- Under the default
[spooler.settle]auto-settle worker a settled message is unpinned past its window (keep_pin = false), so trustlessly reading an old, settled message depends on the operator having kept the pin (keep_pin = true).
Trustless reply and compose
Viewing is no longer the fallback’s whole surface: the trustless
reader’s header carries a Reply on-chain button, and the mail view
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 taskpane 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 pane’s wasm module, pin
the sealed bytes to your configured IPFS pin service, then sign and
submit the SendMail transaction. The same seam ships in the
Thunderbird extension.
A reply sent this way is an on-chain send, not a host SMTP compose:
it never touches the Outlook account’s own outgoing 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 account-api’s
/v1/chain like every other on-chain action in the pane, and as
always the relay cannot alter what you sign.
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. Settings → Balances remains the standing place to buy stamps outside a compose.
Sending needs one endpoint viewing does not: the connection-settings
pane 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. Operators offering that pin surface should read
the pin lifecycle caveat:
a client-made pin sits outside any mail server’s pin lifecycle.