What happens
1. internal/server/tracking.go — POST /e/u/{token} always answers 204
recordUnsubscribe(r.Context(), client, bus, target)
w.WriteHeader(http.StatusNoContent)
recordUnsubscribe has no return value, and it bails out early with a bare return when the destination is empty, the workspace is 0 or the source is empty. This endpoint is the target of the mailbox provider's one-click POST, so Gmail and Yahoo receive 204 and treat the opt-out as done. If the write failed — DB error, race, an empty field after token decoding — the contact stays subscribed and keeps receiving mail, while every layer above believes the unsubscribe succeeded.
ADR 0013 describes recordUnsubscribe as writing both the destination-keyed row and the email.unsubscribed event. Today neither the handler nor its caller can tell whether either of them happened.
2. internal/jobs/broadcast.go:342 — a broadcast can go out without List-Unsubscribe
if url, uerr := tracker.UnsubscribeURL(unsub); uerr != nil {
logging.FromContext(ctx).Error("broadcast: unsubscribe url failed", ...)
} else {
listUnsubURL = url
}
On error listUnsubURL stays empty and the message is still sent, with ListUnsubscribeURL: "" — that is, without the header at all. ADR 0012 treats that header as table stakes and ties it directly to keeping the spam-complaint rate low, so a silent per-recipient fallback to "no header" is the exact outcome the ADR is meant to prevent.
What I would expect
- The handler propagates failure —
recordUnsubscribe returning an error, 5xx on a failed write — so the provider retries instead of marking the opt-out done.
- A recipient whose unsubscribe URL cannot be built fails the same way "contact has no email" already fails, rather than shipping a non-compliant message.
Both are the same shape: a failure that does not stop the flow and is invisible from the outside.
Provenance
Found with ReviewGate (https://reviewgate.dev), a local review gate I build, run over 0d10d33 and re-checked against main. The cross-check against ADR 0012 and 0013 is mine — the run had no access to your rules. Local run, my own model key, nothing left my machine.
What happens
1.
internal/server/tracking.go—POST /e/u/{token}always answers 204recordUnsubscribehas no return value, and it bails out early with a barereturnwhen the destination is empty, the workspace is 0 or the source is empty. This endpoint is the target of the mailbox provider's one-click POST, so Gmail and Yahoo receive 204 and treat the opt-out as done. If the write failed — DB error, race, an empty field after token decoding — the contact stays subscribed and keeps receiving mail, while every layer above believes the unsubscribe succeeded.ADR 0013 describes
recordUnsubscribeas writing both the destination-keyed row and theemail.unsubscribedevent. Today neither the handler nor its caller can tell whether either of them happened.2.
internal/jobs/broadcast.go:342— a broadcast can go out withoutList-UnsubscribeOn error
listUnsubURLstays empty and the message is still sent, withListUnsubscribeURL: ""— that is, without the header at all. ADR 0012 treats that header as table stakes and ties it directly to keeping the spam-complaint rate low, so a silent per-recipient fallback to "no header" is the exact outcome the ADR is meant to prevent.What I would expect
recordUnsubscribereturning an error, 5xx on a failed write — so the provider retries instead of marking the opt-out done.Both are the same shape: a failure that does not stop the flow and is invisible from the outside.
Provenance
Found with ReviewGate (https://reviewgate.dev), a local review gate I build, run over
0d10d33and re-checked againstmain. The cross-check against ADR 0012 and 0013 is mine — the run had no access to your rules. Local run, my own model key, nothing left my machine.