Migration

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.

Updated

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 dataPostscale reasonImport it?
Hard bounceshard_bounceYes
Spam complaints / abuse reportscomplaintYes
UnsubscribesunsubscribeYes
Manual blocks / invalids / global blocksmanualUsually yes
Temporary failures / soft bouncesNot a suppressionUsually no
Opens, clicks, delivery historyNot a suppressionNo

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.

ProviderExport before cutoverPostscale provider valueNotes
SendGridBounces, spam reports, global unsubscribes, group unsubscribes, blocks or invalidssendgridSendGrid suppression categories are distinct from Marketing Campaigns contacts. Keep each suppression type separate when possible.
MailgunBounces, complaints, unsubscribesmailgunMailgun stores bounces, spam complaints, and unsubscribes as suppression lists per domain.
PostmarkSuppression dump per message streampostmarkPreserve the message stream as scope_type=stream and scope_id=<stream> when unsubscribe scope matters.
ResendSuppression list export or manually prepared CSVresendResend suppresses hard bounces and spam complaints region-wide; import those conservatively as org-wide rows.
Amazon SESAccount-level suppressed destinationssesSES account-level suppressions are region-specific and can be listed through SES v2.
CustomAny CSV with an email columncustomUse 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.

  1. Open /dashboard/suppressions.
  2. Go to Import Suppressions.
  3. Select the provider.
  4. Choose the default reason and scope.
  5. Upload the CSV.
  6. Click Preview.
  7. Review valid, invalid, and duplicate rows.
  8. 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:

  1. Export suppressions from the old provider.
  2. Keep bounces, complaints, unsubscribes, and manual blocks separate if possible.
  3. Remove obvious malformed rows before import.
  4. Preview each CSV in Postscale.
  5. Investigate invalid rows instead of ignoring them.
  6. Commit valid rows.
  7. Spot-check a few known suppressed addresses with /v1/suppressions/check.
  8. Send staging traffic only after the import is complete.

After cutover:

  1. Keep the old provider active until delayed bounces and webhook retries drain.
  2. Re-export suppressions after the final traffic move.
  3. Import the delta into Postscale.
  4. 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.