Suppresses recipients after a hard bounce or spam complaint, distinguishes transient failures, honors List-Unsubscribe, and never retries delivery to a suppressed address.
Package status: reference context ready for human review. The contract and test scenarios are complete, but no claim is made that an adopting implementation has passed them.
Decision
Outbound email is gated by a suppression list. A hard bounce or spam complaint inserts the recipient with reason and provider event id; later campaigns and transactional mail must not select that address except a documented abuse-response channel. Transient failures follow the job-queue retry policy and do not suppress. Commercial messages include List-Unsubscribe and, where advertised, a one-click POST that also writes suppression. Retrying a suppressed address is a contract failure even if the template changed.
Scope
- Classifying provider events as hard bounce, transient bounce, or complaint.
- Suppression writes and reads at enqueue and dequeue.
- List-Unsubscribe and one-click handling.
- Poison-queue replay that cannot bypass the list.
Outside this block
- Template copy and localization.
- Inbox-placement scoring beyond acting on complaints.
- SMS or push channels.
Contract
- Enqueue to an address suppressed for
hard-bounceorcomplaintis rejected withrecipient_suppressedand makes no provider API call. - A hard bounce with a permanent DSN class writes suppression within
apply_within_msand is idempotent onprovider_event_id. - A complaint writes suppression even if no bounce was seen.
- Transient failures increment retry state and do not write suppression.
- Profile-required commercial mail includes
List-Unsubscribeand a one-click POST that authenticates a signed token without a session. - Poison-queue replay checks suppression after dequeue as well as before enqueue.
Implementation
Normalize addresses with the documented case and Unicode profile. Map provider codes from a table. Honor RFC 8058: GET must not unsubscribe. Store a hash plus routing domain if the privacy program forbids raw addresses. Hard-bounce resubscribe requires a new confirmed opt-in.
Failure handling
Invalid webhook signatures are dropped without suppression. Failed suppression writes must not ack the webhook. Expired one-click tokens can still suppress after authentication. Complaint floods still apply per-address; this block does not disable the sending domain.
Verification
Measure enqueue rejects, webhook idempotency, transient-versus-hard mix, and retries targeting suppressed rows (must be zero). Fixture a List-Unsubscribe POST. Execute the packaged scenarios against a provider double.
The executable-looking examples in this package are fixtures and acceptance contracts. Run
python tools/validate.py from the collection root to check package structure and metadata; then
implement and execute the scenarios in the target repository.
Adoption assumptions
- Names and numeric values in
example.yamlare an adoption profile, not universal defaults. - The adopting team must map actors, data classes, error vocabulary, and ownership to its system.