feat: emit a signed Reply-To by default, --anonymous to omit

This commit is contained in:
randogoth 2026-09-26 16:45:41 +03:00
parent 5bfb415830
commit 56a6ed1186
2 changed files with 7 additions and 0 deletions

View file

@ -70,6 +70,8 @@ uv run smolmail.py list
uv run smolmail.py read <id> 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.
`import` adds a contact from a `smol://` address without trusting any server, `contacts` lists known keys and how each was learned, and `rotate` replaces the identity with a signed certificate your contacts accept automatically. `import` adds a contact from a `smol://` address without trusting any server, `contacts` lists known keys and how each was learned, and `rotate` replaces the identity with a signed certificate your contacts accept automatically.
Mail is stored sealed and opened on demand, so the local database holds no plaintext. Superseded keys are retained after a rotation, because mail already addressed to them is readable with nothing else. Mail is stored sealed and opened on demand, so the local database holds no plaintext. Superseded keys are retained after a rotation, because mail already addressed to them is readable with nothing else.

View file

@ -680,6 +680,9 @@ def cmd_send(args: argparse.Namespace, store: Store) -> int:
if not sep or not FM_KEY.match(key.strip()): if not sep or not FM_KEY.match(key.strip()):
raise SmolError(f"{raw!r} is not a valid `Key: value` header") raise SmolError(f"{raw!r} is not a valid `Key: value` header")
fields.append((key.strip(), value.strip())) fields.append((key.strip(), value.strip()))
# §5.7: a signed reply address lets a first-time recipient answer us.
if (account := store.account()) is not None and not args.anonymous:
fields.append(("Reply-To", account.uri(me.pk)))
body = build_frontmatter(fields, text).encode() body = build_frontmatter(fields, text).encode()
envelope = seal(me, recipient, body, pad=not args.no_pad) envelope = seal(me, recipient, body, pad=not args.no_pad)
@ -828,6 +831,8 @@ COMMANDS = [
(("--body",), {"help": "message text; read from stdin when omitted"}), (("--body",), {"help": "message text; read from stdin when omitted"}),
(("--header",), {"action": "append", "metavar": "KEY:VALUE", (("--header",), {"action": "append", "metavar": "KEY:VALUE",
"help": "extra frontmatter field; repeatable"}), "help": "extra frontmatter field; repeatable"}),
(("--anonymous",), {"action": "store_true",
"help": "omit the Reply-To field carrying this address (SPEC.md §5.7)"}),
(("--no-pad",), {"action": "store_true", "help": "do not pad to 1 KiB"})]), (("--no-pad",), {"action": "store_true", "help": "do not pad to 1 KiB"})]),
("fetch", cmd_fetch, "retrieve, verify and acknowledge mail", ("fetch", cmd_fetch, "retrieve, verify and acknowledge mail",
[(("--keep",), {"action": "store_true", "help": "do not delete from the server"})]), [(("--keep",), {"action": "store_true", "help": "do not delete from the server"})]),