Start
Deploy as an operator
Three commands and two DNS records. Nothing in your mail software changes, and you stake nothing until you decide to.
Output on this page is real, captured by running the CLI. Addresses are from the documentation range 2001:db8::/32. The only edits are a truncated base64 key, an elided token, and the CLI’s next-step hints and DNS-panel forms, left out for length.
Get the CLI
$ go install github.com/swornmail/swornmail-go/cmd/sworn@latestSource: github.com/swornmail/swornmail-go.
1. Generate a signing key
$ sworn keygen --selector 2026a
selector 2026a
private key 2026a.key (mode 0600 — keep it secret, back it up)
public key dI6VtBXnxGKWqjtrL3rzidPUDEhrKGJXDuVTd3Soco0=The selector is an arbitrary label that lets you rotate keys without downtime — publish a new selector, move traffic to it, then retire the old record. keygen refuses to overwrite an existing key without --force.
2. Generate your records
$ sworn genrecord --domain mailer.example.com --selector 2026a \
--key 2026a.key --prefix 2001:db8:f00::/48 --unit 64
Publish these records for mailer.example.com.
1. key record — the signing key receivers fetch
zone file:
2026a._sworn.mailer.example.com. 3600 IN TXT "v=SWORN1; k=ed25519; pk=dI6VtBX…co0="
2. policy record — the prefixes you stand behind
zone file:
_prefixes._sworn.mailer.example.com. 3600 IN TXT "v=SWORN1; p=2001:db8:f00::/48; u=64; t=y"
3. reverse-tree pointer (optional) — publish in your reverse zone if you
control it; otherwise discovery uses your MTA's forward-confirmed PTR
_sworn.0.0.f.0.8.b.d.0.1.0.0.2.ip6.arpa. 3600 IN TXT "v=SWORN1; d=mailer.example.com"
notes:
- t=y is set, so this is observe-only: receivers report sworn=none policy.testing=y
and stake no reputation on you, for credit or blame. Watch your traffic, then
re-run with --testing=false to accept accountability.It also prints a DNS-panel form of each record for providers without zone-file editing, and --json for feeding a provider API. Every rule on the records reference is validated before anything is printed, so a mistake surfaces here rather than in production.
You start in testing mode whether you ask to or not. genrecord emits t=y unless you pass --testing=false. Leaving observe-only is a deliberate act.
The prefixes you publish are the prefixes you answer for — every address in them, including any whose reverse DNS you have delegated. Publish only what you operate directly; see accountability.
3. Publish, then check
$ sworn record mailer.example.com --selector 2026a
$ sworn discover --ip 2001:db8:f00:1234::25record fetches and lints what you published. discover runs the same Mode 1 discovery a receiver runs, from one of your MTA’s addresses, and tells you which operator and unit it resolves to. Run it from a machine that can resolve public DNS. If you can, sign the zone with DNSSEC: receivers that validate can then rule out a spoofed answer — see trust in DNS.
Checking a token, and what failure looks like
$ TOKEN=$(sworn sign --key 2026a.key --selector 2026a \
--domain mailer.example.com --prefix 2001:db8:f00::/48)
$ sworn verify $TOKEN --ip 2001:db8:f00:1234::25 \
--policy 'v=SWORN1; p=2001:db8:f00::/48; u=64' --key dI6VtBX…co0=
sworn=pass op=mailer.example.com unit=2001:db8:f00:1234::/64 observed=2001:db8:f00:1234::/64
$? 0
# the same token, presented from outside the attested prefix
$ sworn verify $TOKEN --ip 2001:db8:999::25 \
--policy 'v=SWORN1; p=2001:db8:f00::/48; u=64' --key dI6VtBX…co0=
sworn=fail reason=off_prefix
$? 1
# the policy genrecord actually published, still in testing mode
$ sworn verify $TOKEN --ip 2001:db8:f00:1234::25 \
--policy 'v=SWORN1; p=2001:db8:f00::/48; u=64; t=y' --key dI6VtBX…co0=
sworn=none policy.testing=y policy.wouldbe=pass op=mailer.example.com unit=2001:db8:f00:1234::/64 observed=2001:db8:f00:1234::/64
$? 4--policy and --key make verify fully offline. Without them it fetches both records from DNS — the policy first, and the key only if the policy authorises the token. Tokens last an hour by default. Exit codes: 0 pass · 1 fail · 2 permerror or usage · 3 temperror · 4 none. Scriptable.
This proves your key and policy work. Presenting tokens over SMTP is Mode 2, which needs an MTA that speaks the extension; Mode 1 needs nothing beyond the records.
4. When you are ready, accept accountability
Watch your traffic in testing mode for as long as you like. When the results look right, re-run genrecord with --testing=false and publish the updated policy record. That one change is the moment the oath is taken, and it is entirely yours to make — and to reverse, by republishing t=y.
Verifying as a receiver
If you run inbound mail, see receive with Postfix to set up the milter, implementations for the rspamd module, and reputation semantics for the rules you must follow when acting on a result. The short version: never treat a failure as worse than no attestation at all, and key reputation on the observed /64, not on the unit the operator declared, unless you hold independent evidence that the operator controls the wider prefix.