Suppression List Migration Guide
Import bounces, complaints, unsubscribes, and manual blocks from SendGrid, Mailgun, Postmark, Resend, Amazon SES, or a custom CSV before switching email providers.
TL;DR
Import suppressions before the first production send. Export hard bounces, spam complaints, unsubscribes, and manual blocks from the old provider, preview the CSV in Postscale, commit only valid rows, and keep the old provider active until delayed events have drained.
What you will learn
- Know which suppression data to migrate
- Map provider-specific suppression reasons into Postscale reasons
- Use the dashboard or API preview flow before committing rows
Suppression lists are the recipient addresses you should not mail again. During an email provider migration, they are easy to forget because the main send API still works without them.
That is the risk. If you switch providers without moving suppressions, your new provider has no memory of the addresses that already hard-bounced, complained, or unsubscribed in the old system.
What to import
Move suppression data before the first real send through Postscale.
| Old-provider data | Postscale reason | Import it? |
|---|---|---|
| Hard bounces | hard_bounce | Yes |
| Spam complaints / abuse reports | complaint | Yes |
| Unsubscribes | unsubscribe | Yes |
| Manual blocks / invalids / global blocks | manual | Usually yes |
| Temporary failures / soft bounces | Not a suppression | Usually no |
| Opens, clicks, delivery history | Not a suppression | No |
Hard bounces and complaints are always org-wide in Postscale. Unsubscribes can be org-wide or scoped to a domain, stream, template, or list when the import file includes scope columns.
Provider mapping
Export each provider list separately when the old provider supports it. Separate files make the reason mapping auditable and reduce mistakes.
| Provider | Export before cutover | Postscale provider value | Notes |
|---|---|---|---|
| SendGrid | Bounces, spam reports, global unsubscribes, group unsubscribes, blocks or invalids | sendgrid | SendGrid suppression categories are distinct from Marketing Campaigns contacts. Keep each suppression type separate when possible. |
| Mailgun | Bounces, complaints, unsubscribes | mailgun | Mailgun stores bounces, spam complaints, and unsubscribes as suppression lists per domain. |
| Postmark | Suppression dump per message stream | postmark | Preserve the message stream as scope_type=stream and scope_id=<stream> when unsubscribe scope matters. |
| Resend | Suppression list export or manually prepared CSV | resend | Resend suppresses hard bounces and spam complaints region-wide; import those conservatively as org-wide rows. |
| Amazon SES | Account-level suppressed destinations | ses | SES account-level suppressions are region-specific and can be listed through SES v2. |
| Custom | Any CSV with an email column | custom | Use reason, scope_type, and scope_id columns when possible. |
Provider documentation: SendGrid suppressions, Mailgun suppressions, Postmark suppressions, Resend suppressions, and Amazon SES account-level suppressions.
CSV format
The import API expects CSV text in the content field. If your provider gives
you a JSON export, convert it to CSV first.
At minimum, provide an email column:
email,reason
user@example.com,hard_bounce
abuse@example.com,complaint
old-subscriber@example.com,unsubscribe
blocked@example.com,manual
Accepted email columns include email, recipient, address,
email_address, recipient_email, and to.
Accepted reason columns include reason, type, event, status,
suppression_reason, bounce_type, and category. Postscale recognizes
provider terms like bounce, hard bounce, complaint, spam report, abuse,
unsubscribe, manual, block, invalid, and suppressed.
For scoped unsubscribes, include scope fields:
email,reason,scope_type,scope_id
user@example.com,unsubscribe,list,product-updates
customer@example.com,unsubscribe,stream,marketing
Supported scope types are org, domain, stream, template, and list.
Rows without scope default to org-wide. Hard bounces, complaints, and manual
entries are imported as org-wide even when scope columns are present.
Dashboard import flow
Use the dashboard when you want to review a provider export manually.
- Open
/dashboard/suppressions. - Go to Import Suppressions.
- Select the provider.
- Choose the default reason and scope.
- Upload the CSV.
- Click Preview.
- Review valid, invalid, and duplicate rows.
- Commit the import only after the preview looks right.
Committed rows are idempotent. Existing suppressions are skipped rather than
duplicated. Imported rows are tagged with a source like import:sendgrid,
import:mailgun, or import:ses.
API import flow
Create a preview first:
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": "sendgrid-bounces.csv",
"default_reason": "hard_bounce",
"content": "email,reason\nuser@example.com,bounce\n"
}'
The response includes an import job id plus row counts:
{
"job": {
"id": "7a24c19f-84a3-4cc8-9b60-7f77b6842cf2",
"status": "previewed",
"total_rows": 1,
"valid_rows": 1,
"invalid_rows": 0,
"duplicate_rows": 0
},
"items": []
}
Commit after review:
curl -X POST https://api.postscale.io/v1/imports/suppressions/jobs/7a24c19f-84a3-4cc8-9b60-7f77b6842cf2/commit \
-H "Authorization: Bearer ps_live_your_api_key"
See the Suppressions API for request and response details.
Migration checklist
Before cutover:
- Export suppressions from the old provider.
- Keep bounces, complaints, unsubscribes, and manual blocks separate if possible.
- Remove obvious malformed rows before import.
- Preview each CSV in Postscale.
- Investigate invalid rows instead of ignoring them.
- Commit valid rows.
- Spot-check a few known suppressed addresses with
/v1/suppressions/check. - Send staging traffic only after the import is complete.
After cutover:
- Keep the old provider active until delayed bounces and webhook retries drain.
- Re-export suppressions after the final traffic move.
- Import the delta into Postscale.
- Watch bounce and complaint rates by ISP for at least two weeks.
Common mistakes
Do not import every old event as a hard bounce. A temporary deferral is not the same thing as a permanent delivery failure.
Do not merge unsubscribes and complaints without keeping reason data. A spam complaint is more serious than a normal unsubscribe and should remain auditable.
Do not delete the old provider immediately. Delayed bounces and webhook retries can still arrive after you stop sending through it.
Do not use marketing contact exports as suppression exports. Contacts and suppressions are separate data sets in many providers.
Do not import migrated hard bounces or complaints by posting rows one at a time
to POST /v1/suppressions. That endpoint is only for manual blocks and scoped
unsubscribes. Use the preview/commit import endpoints for provider migration
data.
Frequently asked questions
- Should I import suppressions before or after switching traffic?
- Before. Import them before staging or production sends so previously bounced, complained, or unsubscribed recipients are not contacted again.
- Should soft bounces be imported?
- Usually no. Soft bounces are temporary failures. Import hard bounces, complaints, unsubscribes, and intentional manual blocks.
Related guides
Put the guide into production
Postscale brings sending, inbound processing, DMARC reporting, and masked addresses behind one API so the operational pieces stay connected.