-
v0.24.0
Stablereleased this
2026-08-20 00:44:37 -05:00 | 38 commits to main since this releaseThe server could see that a sender offers a one-click unsubscribe and could not use
it.get_messagehas reportedunsubscribe: {one_click: true}since 0.19.0, so the
answer to "unsubscribe me from this" was "tap the button in Mail" — which is one tap, and
fine. The reason to build the tool anyway is the gate, not the convenience: hitting an
unsubscribe endpoint on a hostile message confirms to the sender that the mailbox is live
and read. A person tapping Unsubscribe cannot tell whether the sender authenticated. This
server already computes that, so it can decline to be the thing that answers a phish.Added
-
unsubscribe_message, performing the RFC 8058 one-click POST for one message,
behindMAIL_UNSUBSCRIBE_ENABLED. Off by default, and off means the tool is not
registered rather than registered and refusing — the ruleMAIL_WRITABLE_FOLDERS
already follows, and it matters more here, because the fallback when the tool does not
exist is one tap in Mail.It takes a
message_idand has no URL parameter. The URI is read server-side from
the message's own DKIM-signed header. A tool that POSTs to a URL it is handed is an open
POST proxy reachable by anything that can talk to this server, including text sitting in
an email — so the door is closed by there being no parameter to push on, and a test
pins the argument schema.The request follows the RFC's constraints rather than a generic POST: no cookies, no
authorization, no redirects followed (senders MUST NOT return one, and chasing it would
re-POST this mailbox's token to a host the signature never covered),multipart/form-data
per the SHOULD, and exactly one attempt — a retry against a stranger's endpoint is a
second unsubscribe request, not another go at the first. -
A gate scored against the live account before it was written, which is the only
reason it is the right shape. Probe 9 (2026-08-20, 320 messages across every folder, 16
one-click) ran the designed rules over real mail and refused 11 of the 16, including
the clean, DKIM-aligned,p=quarantinesender the design named as the case that must
proceed. Two conditions were wrong:-
"no DKIM verdict may be unaligned" refuses ordinary bulk mail. An ESP signs with
its ownd=alongside the sender's, so a correctly aligned message carries an
unaligned verdict too. The property actually wanted is "the From domain is
authenticated", which isdmarc=pass— already parsed, already trust-keyed, and true
via SPF alignment even where no DKIM signature aligns. -
"any hygiene flag refuses" refuses newsletters.
reply_to_elsewhereis what
"send from no-reply, reply to support" produces andrecipient_in_unsubscribeis what
every ESP does by construction. Together they refused a Constant Contact newsletter
published underp=reject. Both are now warnings; every other flag blocks.
The flags that survive as blocking are the ones describing deception, and they are
what caught the phish.p=nonewarns and never refuses — 11 of the 16 live one-click
senders publish it, including every obviously legitimate one. -
-
A DKIM
h=coverage check on the offer itself. RFC 8058 requires both unsubscribe
headers to be covered by a valid signature, so coverage proves the URI was not injected
in transit. Read only off a signature whosed=matches aheader.dthat a trusted
verdict recorded as passing — readingh=off an arbitrary signature proves nothing.
Three states, not two: covered, not covered, and no verified signature to read, with the
last warning rather than refusing. Four of sixteen live messages carry two verified
signatures; they agreed in every case, so any one covering both is enough and no
tiebreak rule exists to be disagreed with later.What it does not buy, stated because the temptation is real: the phish signs its own
mail correctly and its unsubscribe header is validly signed too. Integrity, not good
faith. -
allow_unauthenticated, scoped to authentication refusals only. It carries a
dmarc=fail— four of the sixteen live messages are senders that fail DMARC while
passing SPF, and a user may well recognise them — and it can never carry a hygiene flag
or an unsigned offer. "Unauthenticated but I know who they are" is a judgement a person
can make; "this message is impersonating someone" is not, and the argument is
model-settable, so the text arguing for it could come from the message being
unsubscribed from. -
mail/unsubscribeas a log phase. Its own, for the reasonimap/writeis separate
fromimap/fetch: this is the only request this server makes to a host it did not
choose, and "did this server contact that sender" must be answerable without reading
every mail line. The URI never appears — only its hostname. The query string is a bearer
token identifying this mailbox, which makes it account data by the rule inclient.py,
and it stays out of tool responses for the same reason.
Fixed
- A
List-Unsubscribeheader that is present and empty is no longer reported as
unreadable. Eleven messages in the live INBOX arrive that way — confirmed against a
fullBODY.PEEK[]read, so it is the senders and not the fetch.unparsed: truereads
to a model as "there is an unsubscribe here I could not read", which has it tell the
user a facility might exist. The sender advertised nothing, so the true sentence is the
one absence produces. The three states are unchanged and the middle one still means what
it says: content that defeated the parser, not the absence of content.
Not built, deliberately
- No bulk mode. One message per call. A loop over a folder announces the mailbox as
live to every sender in the batch, which is the exact outcome the gate exists to avoid. - No
mailto:unsubscribe. This server cannot send mail — that is M3. It is not a
small gap: 24 of the 40 live senders offering any facility offer only a mailto, more
than the 16 this tool covers. Reported by name rather than failing opaquely. - No auto-move to junk afterwards. Junking is a separate call, and this stays atomic.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-