80 lines
5 KiB
Markdown
80 lines
5 KiB
Markdown
# smol mail
|
|
|
|
[](https://ai-declaration.md)
|
|
[](https://creativecommons.org/licenses/by-sa/4.0/)
|
|
[](https://opensource.org/licenses/Apache-2.0) 
|
|
|
|
A minimalist, decentralised, end-to-end encrypted mail protocol.
|
|
|
|
Online correspondence reduced to an address, a key and a message.
|
|
|
|
Five operations, four primitives, zero extensibility mechanisms. One 32-byte master key as the only identity. No certificates, no CAs, no expiry: one round trip and the server's key pin are the entire trust model. A fresh key seals every message: untraceable, unforgeable, and safe by construction.
|
|
|
|
Anti-spam without censorship: quotas and tokens do the filtering, and servers never see a word. Clients talk directly to the recipient's server. Registration is bound to one server by proof-of-possession. Double-signed certificate chains move a username to a new key, and old keys keep receiving until every contact catches up.
|
|
|
|
## Identity
|
|
|
|
The only secret is a 32-byte master. Back that up and nothing else: the Ed25519 signing key, the encryption key derived from it, and the key that issues accept tokens all come out of it. One thing to hold, move between servers, print on paper or scan from a screen.
|
|
|
|
Signatures prove authorship. A throwaway key per message means a stolen identity key cannot decrypt anything already sent.
|
|
|
|
Rotating a key advances an index rather than inventing an unrelated key, so the master alone can re-derive every key you have ever used. That is what makes mail addressed to a superseded key readable years later, with nothing archived. `restore` rebuilds a client from the master and a username.
|
|
|
|
## Addressing
|
|
|
|
```
|
|
alice@example.org short form, resolved via the server
|
|
smol://alice@example.org/mfrggzdfmztwq2lknnwg23tpo… self-certifying, carries its own key
|
|
```
|
|
|
|
The short form is typeable. The long form carries the key itself, so an address shared by QR code, contact file or link needs no trust in any server at all. Either way, changing servers never changes an identity.
|
|
|
|
## Cryptography
|
|
|
|
Ed25519 · X25519 · ChaCha20-Poly1305 · SHA-256. Four established primitives, no novel cryptography, nothing else anywhere in the protocol. Transport is TCP with a Noise handshake. No certificates, no CA, no expiry.
|
|
|
|
## Reference server
|
|
|
|
[smolmaild.py](smolmaild.py) is a complete server in one file. It declares its own dependencies inline, so there is nothing to install:
|
|
|
|
```
|
|
uv run smolmaild.py keygen
|
|
uv run smolmaild.py serve
|
|
```
|
|
|
|
`keygen` writes the server's static key to `server.key` and prints its public key in base32. Publish that public key through a trusted channel. Clients pin it, and a mismatch aborts the handshake.
|
|
|
|
`serve` listens on `127.0.0.1:1961` by default and stores mail in `mail.db`. Use `--host 0.0.0.0` to accept remote connections, and `--invite-token` to close registration. `--help` lists the size, quota, retention and rate limits, including the separate `--requests-quota` for mail that arrives without an accept token.
|
|
|
|
## Reference client
|
|
|
|
[smolmail.py](smolmail.py) is a complete client in one file, with the same inline dependencies:
|
|
|
|
```
|
|
uv run smolmail.py keygen
|
|
uv run smolmail.py trust example.org <server-key>
|
|
uv run smolmail.py register alice@example.org
|
|
uv run smolmail.py resolve bob@example.org
|
|
echo "hello" | uv run smolmail.py send bob@example.org --subject Hi
|
|
uv run smolmail.py fetch
|
|
uv run smolmail.py list
|
|
uv run smolmail.py read <id>
|
|
```
|
|
|
|
Sent mail carries the sender's full `smol://` address in a signed `Reply-To` field (SPEC.md §5.7), so a first-time recipient can answer without an out-of-band channel. Pass `--anonymous` to omit it.
|
|
|
|
`accept` admits a contact to your mailbox proper and pushes the token to your server at once. `block` withdraws it. `list --requests` shows what arrived without one. `import` adds a contact from a `smol://` address without trusting any server, `contacts` lists known keys and how each was learned, and `rotate` advances the identity with a certificate signed by both keys, which your contacts accept automatically.
|
|
|
|
`fetch` deletes what it has verified and stored. `fetch --keep` leaves mail on the server and remembers where it stopped, and `--reset` pages through it again.
|
|
|
|
Mail is stored sealed and opened on demand, so the local database holds no plaintext.
|
|
|
|
## Specification
|
|
|
|
[SPEC.md](SPEC.md) defines the wire format, the operations and the trust model.
|
|
|
|
## License
|
|
|
|
The protocol definition, [SPEC.md](SPEC.md), is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license ([CC BY-SA-4.0](LICENSES/CC-BY-SA-4.0.txt)).
|
|
|
|
The reference code, `smolmail.py`, `smolmaild.py`, `smolmail_rns.py` and `smolmaild_rns.py`, is licensed under the Apache License 2.0 ([Apache-2.0](LICENSES/Apache-2.0.txt)).
|