45 lines
1.8 KiB
Markdown
45 lines
1.8 KiB
Markdown
# OpenSpec Change: enterprise-suppression
|
|
|
|
- **Change ID**: enterprise-suppression
|
|
- **Package**: `@suaveplan/suppression`
|
|
- **Category**: universal
|
|
- **Target path**: `universal/email/suppression`
|
|
- **Status**: PROPOSED
|
|
- **Date**: 2026-04-19
|
|
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/suppression.md`
|
|
|
|
## Summary
|
|
|
|
Email suppression list manager — classifies bounces, records unsubscribes, filters outgoing sends, and generates signed one-click unsubscribe links per RFC 8058. Pluggable store.
|
|
|
|
## Current-state audit (genesis)
|
|
|
|
Package does not exist yet — fresh build.
|
|
|
|
## Dependencies
|
|
|
|
- Workspace: `@suaveplan/error`, `@suaveplan/events`, `@suaveplan/types`
|
|
- External (via catalog): `zod`, `tslib`, `@opentelemetry/api`
|
|
- Dev (via catalog): `vitest`, `@vitest/coverage-istanbul`, `fast-check`, `mitata`
|
|
|
|
## Preserved public surface (star-exported from main)
|
|
|
|
- `BounceClassifier`, `classifyBounce`, `BounceType`
|
|
- `SuppressionFilter` — filters an outgoing send list against the store.
|
|
- `SuppressionManager` — CRUD over suppressions with reason tagging.
|
|
- `UnsubscribeLink` — `generateLink`, `verifyLink`, `rotateSecret`.
|
|
- `SuppressionStore` interface + `MemorySuppressionStore`.
|
|
- Types: `SuppressionReason`, `SuppressionEntry`, `BounceReport`, `FilterResult`, `LinkOptions`.
|
|
|
|
## New genesis additions
|
|
|
|
- Codes: `SUPPRESSION.INVALID_ADDRESS`, `SUPPRESSION.STORE_ERROR`, `SUPPRESSION.LINK_EXPIRED`, `SUPPRESSION.LINK_INVALID_SIGNATURE`.
|
|
- Zod schemas for every type.
|
|
- OTel counter `suppression.filter.total{outcome}`, `suppression.suppressed.total{reason}`.
|
|
- Property tests: every classified hard bounce remains suppressed; round-trip of `generateLink → verifyLink`.
|
|
|
|
## Impact
|
|
|
|
- Adds one new package: `universal/email/suppression`
|
|
- Adds one new spec capability: `suppression`
|