Suppressions
The tenant-scoped suppression list that blocks sends to bounced or complained addresses, auto-populated from provider webhooks and checked by the sequence engine before every send.
Suppressions
The suppression list is Pact's do-not-send registry. Any address on it is
excluded from sends until you explicitly remove it. The list is tenant-scoped
(email_suppressions table), auto-populated from email-provider webhooks, and
consulted by the sequence engine before every send. Admin management lives at
/v1/integrations/email-suppressions and requires admin or owner role.
How addresses get suppressed
Suppressions arrive two ways:
- Automatically — the webhook handler in
core/email/webhook_handler.pynormalizes bounce and complaint events from Resend, SendGrid, Postmark, Mailgun, and SES into a common shape, then auto-suppresses on hard bounces and spam complaints. The originating event id is stored insource_event_idso you can trace a suppression back to the exact webhook. - Manually — an admin adds an address directly (reason defaults to
manual).
Hard bounces vs. soft bounces
Only permanent failures auto-suppress. A hard bounce (invalid mailbox) or an abuse/spam complaint lands the address on the list; transient soft bounces are recorded as events but do not suppress, so a full inbox or a temporary outage does not permanently block a real contact.
List and filter
GET /v1/integrations/email-suppressions?reason=hard_bounce&limit=100
Returns active suppressions (where unsuppressed_at IS NULL), newest first,
optionally filtered by reason. Each row includes the email, reason,
source_event_id, and suppressed_at.
Add manually
POST /v1/integrations/email-suppressions
{ "email": "[email protected]", "reason": "manual" }
Adds (or re-suppresses) an address. The insert is idempotent — re-adding a
previously unsuppressed address clears its unsuppressed_at and refreshes the
timestamp. An email.suppression.added audit event is written.
Remove (unsuppress)
DELETE /v1/integrations/email-suppressions/{suppression_id}
This is a soft removal — it stamps unsuppressed_at rather than deleting the
row, preserving history. An email.suppression.removed audit event captures the
address.
Unsuppress deliberately
Removing a hard-bounced or complained address puts it back in your sendable pool. If it bounces or complains again, your sender reputation takes a second hit — and the webhook will simply re-suppress it. Only unsuppress addresses you have a concrete reason to believe are now valid.
Batch check (used by the sequence engine)
POST /v1/integrations/email-suppressions/check
{ "emails": ["[email protected]", "[email protected]"] }
Returns two arrays — suppressed and deliverable. The sequence engine calls
this before a send to split a batch into addresses it may mail and addresses it
must skip. This endpoint requires authentication but not the admin role, because
it is a read-only pre-send check rather than an administrative mutation.