Suppressions API
Suppressions API
Manage the email suppression list for your organization. Suppressed addresses are automatically skipped during sending to protect your sender reputation.
Hard bounces, spam complaints, and one-click unsubscribes automatically add entries to the suppression list. You do not need to manage these manually.
List Suppressions
GET /v1/suppressions
Retrieve a paginated list of suppression entries.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
query | string | Case-insensitive search by email |
reason | string | Filter by hard_bounce, complaint, manual, or unsubscribe |
scope_type | string | Filter by org, domain, stream, template, or list |
scope_id | string | Filter by scope identifier |
source | string | Filter by source, such as manual, unsubscribe, delivery_event, or import:sendgrid |
limit | integer | Number of results to return (default: 50, max: 200) |
offset | integer | Number of results to skip (default: 0) |
Example Request
curl -X GET "https://api.postscale.io/v1/suppressions?limit=50&offset=0" \
-H "Authorization: Bearer ps_live_your_api_key"
Response
{
"suppressions": [
{
"id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"email": "bounced@example.com",
"reason": "hard_bounce",
"source_message_id": "msg_abc123",
"bounce_code": "550",
"scope_type": "org",
"source": "delivery_event",
"created_at": "2026-01-15T08:20:00Z"
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"email": "complained@example.com",
"reason": "complaint",
"source_message_id": "msg_def456",
"feedback_type": "abuse",
"scope_type": "org",
"source": "delivery_event",
"created_at": "2026-01-16T14:45:00Z"
}
],
"total": 42,
"limit": 50,
"offset": 0
}
Check Suppression
GET /v1/suppressions/check
Check whether a specific email address is on the suppression list.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
email | string | Yes | The email address to check |
Example Request
curl -X GET "https://api.postscale.io/v1/suppressions/check?email=bounced@example.com" \
-H "Authorization: Bearer ps_live_your_api_key"
Response (suppressed)
{
"email": "bounced@example.com",
"suppressed": true
}
Response (not suppressed)
{
"email": "user@example.com",
"suppressed": false
}
Add Suppression
POST /v1/suppressions
Manually add an email address to the suppression list. This endpoint only accepts manual and unsubscribe.
Use the import preview/commit endpoints for migrated hard bounces and complaints. Direct API additions with reason: "hard_bounce", reason: "complaint", or provider terms like reason: "bounce" are rejected.
Manual entries are org-wide. You can also add scoped unsubscribe entries by passing reason: "unsubscribe".
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | The email address to suppress |
reason | string | No | manual or unsubscribe (default: manual) |
scope_type | string | No | Scope for unsubscribe entries: org, domain, stream, template, or list |
scope_id | string | Conditional | Required for non-org unsubscribe scopes |
Example Request
curl -X POST https://api.postscale.io/v1/suppressions \
-H "Authorization: Bearer ps_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com"
}'
Response
{
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"email": "user@example.com",
"reason": "manual",
"scope_type": "org",
"source": "manual",
"created_at": "2026-01-18T10:30:00Z"
}
Remove Suppression
DELETE /v1/suppressions/:identifier
Remove a suppression entry. Prefer passing the suppression entry id; passing an email address is retained for backwards compatibility.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
identifier | string | Yes | Suppression entry UUID, or legacy email address |
Example Request
curl -X DELETE https://api.postscale.io/v1/suppressions/f47ac10b-58cc-4372-a567-0e02b2c3d479 \
-H "Authorization: Bearer ps_live_your_api_key"
Response
{
"status": "removed"
}
Import Suppressions
POST /v1/imports/suppressions/preview
Preview a provider CSV export before writing entries. Supported provider values are sendgrid, mailgun, postmark, resend, ses, and custom.
You can also run the same flow from the dashboard at /dashboard/suppressions under Import Suppressions. The dashboard uses the same preview-and-commit API: rows are parsed first, reviewed, and only written after commit.
What to import
| Old provider data | Postscale reason |
|---|---|
| Hard bounces, permanent failures | hard_bounce |
| Spam complaints, abuse reports, feedback loop complaints | complaint |
| Unsubscribes, global unsubscribes, list unsubscribes | unsubscribe |
| Manual blocks, invalids, blocked recipients | manual |
Do not import temporary failures or soft bounces as permanent suppressions unless the old provider already promoted them to a hard bounce or manual block.
CSV columns
The import request's content field must contain CSV text. If your provider gives you JSON, convert it to CSV before calling the preview endpoint.
The importer accepts common provider export shapes. Email can be supplied in email, recipient, address, email_address, recipient_email, or to.
Reasons can be supplied in reason, type, event, status, suppression_reason, bounce_type, or category. Provider terms such as bounce, hard bounce, complaint, spam report, abuse, unsubscribe, manual, block, invalid, and suppressed are normalized to Postscale reasons.
For scoped unsubscribes, include scope_type and scope_id, or provider-specific columns such as domain, stream, message_stream, template, template_id, list, or list_id. Supported scope types are org, domain, stream, template, and list.
curl -X POST https://api.postscale.io/v1/imports/suppressions/preview \
-H "Authorization: Bearer ps_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"provider": "sendgrid",
"source_filename": "bounces.csv",
"default_reason": "hard_bounce",
"content": "email,reason\nuser@example.com,bounce\n"
}'
The preview returns an import job, row counts, and normalized rows with statuses valid, invalid, or duplicate.
| Preview status | Meaning |
|---|---|
valid | Row can be committed |
invalid | Row is missing a usable email, reason, or required scope id |
duplicate | Row duplicates another row in the same import file |
Commit valid rows after review:
curl -X POST https://api.postscale.io/v1/imports/suppressions/jobs/IMPORT_JOB_ID/commit \
-H "Authorization: Bearer ps_live_your_api_key"
Committed rows are idempotent. Existing suppressions are marked skipped; new rows are written with source import:<provider>, such as import:sendgrid or import:mailgun.
For a full provider-by-provider migration checklist, see the Suppression List Migration Guide.
Suppression Reasons
| Reason | Description |
|---|---|
hard_bounce | The email address returned a permanent delivery failure |
complaint | The recipient reported the email as spam |
manual | The address was manually added via the API or dashboard |
unsubscribe | The recipient used a one-click unsubscribe link |
Unsubscribe Scope
Hard bounces, complaints, and manual suppressions are org-wide. Unsubscribes can be scoped when sending non-transactional mail:
{
"unsubscribe_scope": {
"type": "stream",
"id": "product-updates"
}
}
Supported scope types are org, domain, stream, template, and list. If no scope is provided, one-click unsubscribe defaults to org.
Platform-level global suppressions are managed internally by Postscale operators and are not exposed through the organization suppression API.