Files
Richard Courtman 47b8664f0b Classify notification failures from senders, not error prose
Terminal notification failures are 36% of resolved delivery outcomes
fleet-wide (126,337 dead-lettered against 224,692 delivered in the week
to 2026-09-03, over 6,668 clean installs), and the category breakdown
could not say why: unknown was the modal bucket at 31,218.

The class was being derived by substring-matching the Go error message.
That fails in two ways. Any failure whose text carries none of the ~50
recognised tokens falls through to unknown, which is most of what SMTP
produces: net/smtp reports the server's verdict as a reply code, and only
535 was ever matched, so a 550 relay refusal and a 451 temporary failure
both recorded as unknown. Worse, the text being matched includes the
destination's own response body, so a third party can choose the reason
code Pulse records and shows the operator - a 500 whose body contains
"rate limit" was recorded as rate_limited rather than server_error.

Senders now declare the class where they already know it, and the
classifier reads Go's own error types before it reads any prose:
*textproto.Error for SMTP reply codes, x509 and tls for certificate
failures, net.DNSError and timeouts for connectivity. HTTP status codes
set the class at the five sites that build a status error, so the
response body is preserved for the operator's audit row but can no longer
influence the classification. Prose matching remains only as the last
resort for paths that declare nothing.

SMTP 5xx is deliberately not mapped the way HTTP 5xx is: a 550 is the
destination refusing the message, not the destination breaking, so only
the transient 4xx replies count as server_error.

Registers the wider finding as a coverage gap. The 36% is concentration,
not breadth - 72 installs that delivered nothing at all in seven days
account for half of all terminal failures, and Pulse neither backs off
nor tells those operators the destination has never once succeeded.
2026-09-03 23:42:50 +01:00
..
2026-09-01 01:02:17 +01:00