← Back to docs

Suppressions API

Suppressions API

Manage the email suppression list for your organization. Suppressed addresses are automatically skipped during sending to protect your sender reputation.

Automatic suppression

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

ParameterTypeDescription
querystringCase-insensitive search by email
reasonstringFilter by hard_bounce, complaint, manual, or unsubscribe
scope_typestringFilter by org, domain, stream, template, or list
scope_idstringFilter by scope identifier
sourcestringFilter by source, such as manual, unsubscribe, delivery_event, or import:sendgrid
limitintegerNumber of results to return (default: 50, max: 200)
offsetintegerNumber 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

ParameterTypeRequiredDescription
emailstringYesThe 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

FieldTypeRequiredDescription
emailstringYesThe email address to suppress
reasonstringNomanual or unsubscribe (default: manual)
scope_typestringNoScope for unsubscribe entries: org, domain, stream, template, or list
scope_idstringConditionalRequired 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

ParameterTypeRequiredDescription
identifierstringYesSuppression 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 dataPostscale reason
Hard bounces, permanent failureshard_bounce
Spam complaints, abuse reports, feedback loop complaintscomplaint
Unsubscribes, global unsubscribes, list unsubscribesunsubscribe
Manual blocks, invalids, blocked recipientsmanual

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 statusMeaning
validRow can be committed
invalidRow is missing a usable email, reason, or required scope id
duplicateRow 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

ReasonDescription
hard_bounceThe email address returned a permanent delivery failure
complaintThe recipient reported the email as spam
manualThe address was manually added via the API or dashboard
unsubscribeThe 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.