Skip to content

feat(suppression): список подавления + per-key send-политика#56

Merged
friench merged 1 commit into
mainfrom
feat/suppression
Jul 6, 2026
Merged

feat(suppression): список подавления + per-key send-политика#56
friench merged 1 commit into
mainfrom
feat/suppression

Conversation

@friench

@friench friench commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Что это

Список подавления получателей + per-key send-политика (закрывает #24). Не доставляем на адреса, которые жёстко баунснули или пожаловались. Отправка на подавлённый адрес блокируется на POST /send (422), если ключ не помечен exempt.

Естественно продолжает #21: свежие bounce-данные автоматически наполняют список.

Surfaces: REST API + dashboard + MCP.

Как работает

  • Список: suppressions (address уникальный, reason: hard_bounce/complaint/manual/unsubscribe, source, note). CRUD на /admin/api/suppressions (admin).
  • Энфорсмент: POST /send — если любой получатель в списке → 422 со списком suppressed (не доставляем частично); проверка пропускается, если у ключа suppressionExempt.
  • Авто-подавление: BounceService на жёсткий возврат добавляет получателя в список (reason hard_bounce). Мягкие возвраты не подавляются.
  • Per-key политика: suppression_exempt на api_keys — задаётся при создании и переключается через PATCH /admin/api/api-keys/:id. Видно в UI (колонка + тумблер).

Изменения

  • Схема: suppressions + api_keys.suppression_exempt + миграция 0018.
  • domain/suppressions: repository (upsert по адресу, findSuppressed) + service (add/remove/filterSuppressed/addFromBounce, нормализация адресов) + serialize; SuppressionDTO.
  • Send-пайплайн: проверка в роуте /send.
  • API-ключи: suppressionExempt в create/serialize/DTO + PATCH-тумблер + UI.
  • Роуты admin/suppressions.ts.
  • UI: страница «Suppressions» (i18n en/ru) + навигация; ApiKeys — колонка + тумблер.
  • MCP: list_suppressions / add_suppression / remove_suppression.
  • Тесты: интеграционные (CRUD, блокировка отправки в т.ч. при нескольких получателях, обход exempt-ключом, авто-подавление из жёсткого баунса, PATCH per-key).

Проверки

typecheck · lint · format:check · vitest (518 ✓) · ui typecheck/build · mcp typecheck/build — всё зелёное. Паритет ключей en/ru новых неймспейсов проверен.

Closes #24

🤖 Generated with Claude Code

Don't deliver to addresses that hard-bounced or complained. Suppressed
recipients block POST /send (422) unless the API key is exempt.

- schema: suppressions table (address unique, reason, source, note) +
  api_keys.suppression_exempt (per-key send policy) + migration 0018
- domain/suppressions: repository (upsert-by-address, findSuppressed) +
  service (add/remove/filterSuppressed/addFromBounce, address normalizing)
- send pipeline: POST /send rejects suppressed recipients with 422 (listing
  them) unless req.apiKey.suppressionExempt
- auto-suppress: BounceService adds hard-bounce recipients to the list
  (wires #21#24); soft bounces are not suppressed
- api keys: suppressionExempt at creation + PATCH /admin/api/api-keys/:id
  toggle; exposed in ApiKeyDTO + UI (column + toggle)
- routes: /admin/api/suppressions (list/add/delete), admin-scoped
- UI: Suppressions page (en/ru i18n) + nav; MCP: list/add/remove_suppression
- tests: suppression integration (CRUD, send blocking incl. multi-recipient,
  exempt bypass, auto-suppress from hard bounce, per-key PATCH)

Closes #24

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@friench friench merged commit 845120f into main Jul 6, 2026
1 check passed
@friench friench deleted the feat/suppression branch July 6, 2026 15:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Suppression lists & per-key send policies

1 participant