A decentralized email client that runs on Freenet — no servers, no accounts, end-to-end encrypted, and rate-limited by Anti-Flood Tokens instead of a central authority.
Messages live in per-user inbox contracts on the Freenet network, encrypted with post-quantum ML-KEM-768 (NIST FIPS 203) and authenticated with ML-DSA-65 (NIST FIPS 204). The UI is a Dioxus web app served from a signed web-container contract, fronted by a stable container contract so the bookmarkable URL never changes between releases.
Status: pre-alpha. The protocol may still change. Don't store anything you'd mind losing.
-
Install Freenet. Follow the Freenet quickstart to get a network-connected node running on
127.0.0.1:7509. -
Open the app at the stable URL:
http://127.0.0.1:7509/v1/contract/web/122f6AR7PyF8d8mhczuNQM4xrLtBf5t8g2iB7PEVT7KC/That contract id is the container contract: a thin pointer contract that serves a tiny HTML+JS loader, which forwards the browser to the webapp. Its id is byte-stable across every release (see #198, #200, #206), so once you bookmark it, future releases land automatically without the URL changing.
The current canonical id is also committed at
published-contract/container-id.txt. -
Create an identity in the app. Your private keys live inside the identity delegate, in the Freenet node sandbox — never in browser storage.
-
Send a message. The first send burns one Anti-Flood Token, minted automatically by your local node.
Three on-chain contracts and one delegate make up the system:
- Container contract (
contracts/facade/) — stable bookmarkable entry point. Its state is a signedFacadePointer { current_app_id, prev_app_ids }; the served payload is a static HTML+JS shell thatpostMessages the gateway shell to navigate to the webapp. Per release its wasm is unchanged — only the pointer state is updated, viafdev execute update --as-statesigned with the production key. - Webapp contract (
contracts/web-container/) — hosts the compiled Dioxus UI. Its contract id ishash(wasm, ed25519_pubkey); the wasm is frozen, so the id staysGeQLXXR8sfGe31BNRJoVxkvGUTnBkufJ4gTaoX5NWnU5and each release only publishes a newly signed UI as its state. - Inbox contract (
contracts/inbox/) — one per user. Stores ML-KEM-encapsulated, ChaCha20-Poly1305 encrypted messages, with ML-DSA-65 signatures on every state delta for ownership verification. Its wasm is frozen inpublished-contract/, so a rebuild can't rotate the contract id and orphan stored mail; a per-identity migration path (#213, #223) re-signs and re-PUTs when deliberate rotation is needed. - Anti-Flood Token contracts + delegate
(
modules/antiflood-tokens/) — mint/burn tokens that gate each message send. Prevents spam without a central authority. - Identity delegate
(
modules/identity-management/) — holds the user's private keys inside the Freenet delegate sandbox, never exposed to the webapp.
A Freenet contract id is hash(wasm, parameters). The webapp contract's
wasm is frozen, so its id is fixed; the container contract is a stable
entry point that can follow the app if it ever has to move. It is a tiny wasm contract that's deliberately
frozen — its lockfile is isolated from the workspace, its rustc version is
pinned in contracts/facade/rust-toolchain.toml, and CI byte-checks every
PR against the committed published-contract/container.wasm. Releases
update the pointer state inside it rather than re-deploying it.
See AGENTS.md for the developer guide: build setup,
feature-flag matrix, publishing pipeline, Playwright tests, and the
manual E2E checklist.
See RELEASING.md for the production release
runbook, including container pointer flips and recovery.
Licensed under the GNU Lesser General Public License v3.0 or later
(LGPL-3.0-or-later). See COPYING.LESSER and
COPYING for the full license text.