A browser based webmail client for smol mail, a minimalist end-to-end encrypted mail protocol
Find a file
randogoth 6c8419aa32 fix: deliver an accept token once, not on every message
Mirrors the same fix in fumi-core. sendMail attached the token to every
message addressed to an accepted contact, so a secret rode in the body of
ordinary mail indefinitely. §5.8 has the owner deliver each token to its
correspondent; it doesn't say every message carries one.

Harmless between people, and fatal for any recipient that republishes what it
receives: a published record is checked by rebuilding the exact bytes the
author signed, and those can't include a token without disclosing it. Someone
who accepted such a service could never post to it again, and accepting a
correspondent is what the rest of the system teaches.

Contacts stored before this field existed count as still owing, so the next
message to each carries the token once more. That is what every message did
before, so nothing that works today can break. Marked only once the
recipient's server has taken the message, so a send that threw leaves the
token still owing. Not cleared by block: the token is derived from the master
and the identity frozen at acceptance, so they keep the value either way.
2026-10-15 09:18:02 +03:00
locales/en-US chore: flatten repository history 2026-10-09 13:47:50 +03:00
nix chore: flatten repository history 2026-10-09 13:47:50 +03:00
scripts chore: flatten repository history 2026-10-09 13:47:50 +03:00
test chore: flatten repository history 2026-10-09 13:47:50 +03:00
web fix: deliver an accept token once, not on every message 2026-10-15 09:18:02 +03:00
.gitignore chore: flatten repository history 2026-10-09 13:47:50 +03:00
AI-DECLARATION.md chore: flatten repository history 2026-10-09 13:47:50 +03:00
bridge.py chore: flatten repository history 2026-10-09 13:47:50 +03:00
devbox.json chore: flatten repository history 2026-10-09 13:47:50 +03:00
devbox.lock chore: flatten repository history 2026-10-09 13:47:50 +03:00
flake.lock chore: flatten repository history 2026-10-09 13:47:50 +03:00
flake.nix chore: flatten repository history 2026-10-09 13:47:50 +03:00
LICENSE chore: flatten repository history 2026-10-09 13:47:50 +03:00
README.md chore: flatten repository history 2026-10-09 13:47:50 +03:00

gsmol

License: Apache-2.0 Built with Devbox AI-DECLARATION: copilot Nix Flake

Smol Mail in a browser tab. All the crypto, keys and mail handling run client-side in JavaScript; a small local bridge just relays bytes to a smolmaild server over WebSocket.

browser (all crypto, all keys)  ⇄  bridge.py (dumb byte pipe)  ⇄  smolmaild (TCP :1961)

The bridge holds no keys and never parses a byte of traffic. The trust boundary is the machine running the browser.

Run

devbox run serve

Open http://127.0.0.1:8096. Onboarding walks you through creating or restoring an identity (the seed is shown once — write it down), pinning your server's public key, and claiming an address. Then fetch.

Non-default server port: uv run bridge.py --allow-port <port>.

Test

devbox run test

Byte-for-byte checks against the reference vectors, protocol cases, and a live end-to-end exchange with the reference client through the bridge — the exact byte path the browser takes. Only clicking through the UI is left to a manual pass.

Deploy

nix build .#default   # -> ./result/bin/gsmol-bridge

The bridge binds to 127.0.0.1 and has no TLS of its own — for anything beyond localhost, put nginx or caddy in front. A NixOS module is included (nixosModules.default → services.gsmol-bridge) with the same options.