Suppressions
Manage global and per-org suppression lists for bounces, complaints, and manual suppressions. Suppressed emails are automatically blocked from sending.
Overview
PushMail automatically maintains a suppression list for each organization. When an email hard-bounces, receives a spam complaint, or is manually suppressed, it's added to the suppression list. Any future send attempts to suppressed addresses are blocked before they reach your email provider, protecting your sender reputation.
Suppressions are checked:
- At the API level when you call
POST /v1/send - In the queue consumer before dispatching campaign and sequence emails
- Using a fast KV cache layer for minimal latency
The suppression object
{
"id": 1,
"orgId": 5,
"siteId": null,
"email": "bounced@example.com",
"reason": "hard_bounce",
"source": "automatic",
"originalSendId": 42,
"createdAt": "2025-03-01T10:00:00.000Z",
"updatedAt": "2025-03-01T10:00:00.000Z"
}Reason values
| Reason | Description |
|---|---|
hard_bounce | Permanent delivery failure (invalid address, domain doesn't exist) |
soft_bounce | Temporary delivery failure that was promoted to suppression |
spam_complaint | Recipient marked the email as spam |
manual | Manually added via API or dashboard |
list_unsubscribe | Recipient used the List-Unsubscribe header |
Source values
| Source | Description |
|---|---|
automatic | Added automatically from webhook events (bounces, complaints) |
manual | Added manually via API or dashboard |
import | Bulk imported from CSV |
List suppressions
GET /v1/suppressions
| Parameter | Type | Description |
|---|---|---|
q | string | Search by email (partial match) |
reason | string | Filter by reason: hard_bounce, soft_bounce, spam_complaint, manual, list_unsubscribe |
page | integer | Page number (default: 1) |
limit | integer | Results per page, 1-100 (default: 50) |
curl "https://pushmail.dev/api/v1/suppressions?reason=hard_bounce&page=1&limit=20" \
-H "Authorization: Bearer pm_live_YOUR_KEY"{
"data": {
"suppressions": [
{
"id": 1,
"email": "bounced@example.com",
"reason": "hard_bounce",
"source": "automatic",
"createdAt": "2025-03-01T10:00:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 1,
"totalPages": 1
}
}
}Add a suppression
POST /v1/suppressions
| Parameter | Type | Description |
|---|---|---|
emailrequired | string | Email address to suppress |
reason | string | Suppression reason. Defaults to "manual" |
siteId | integer | Scope to a specific site (omit for org-wide) |
curl -X POST https://pushmail.dev/api/v1/suppressions \
-H "Authorization: Bearer pm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"reason": "manual"
}'Returns 201 on success. If the email is already suppressed, the existing record is updated.
Check suppression status
GET /v1/suppressions/check/:email
curl "https://pushmail.dev/api/v1/suppressions/check/user@example.com" \
-H "Authorization: Bearer pm_live_YOUR_KEY"{
"data": {
"email": "user@example.com",
"suppressed": true,
"reason": "hard_bounce"
}
}{
"data": {
"email": "user@example.com",
"suppressed": false,
"reason": null
}
}Remove a suppression
DELETE /v1/suppressions/:email
Removes the email from the suppression list, allowing future sends to this address.
curl -X DELETE "https://pushmail.dev/api/v1/suppressions/user%40example.com" \
-H "Authorization: Bearer pm_live_YOUR_KEY"Returns 200 on success, 404 if the email was not suppressed.
Note: The email address in the URL must be URL-encoded (@ becomes %40).
Bulk import
POST /v1/suppressions/import
Import multiple suppressions at once. Accepts either JSON or CSV format.
JSON format
curl -X POST https://pushmail.dev/api/v1/suppressions/import \
-H "Authorization: Bearer pm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"suppressions": [
{ "email": "bad1@example.com", "reason": "hard_bounce" },
{ "email": "bad2@example.com", "reason": "spam_complaint" },
{ "email": "bad3@example.com" }
]
}'CSV format
curl -X POST https://pushmail.dev/api/v1/suppressions/import \
-H "Authorization: Bearer pm_live_YOUR_KEY" \
-H "Content-Type: text/csv" \
-d 'email,reason
bad1@example.com,hard_bounce
bad2@example.com,spam_complaint
bad3@example.com,manual'{
"data": {
"imported": 3,
"skipped": 0,
"total": 3
}
}Maximum 10,000 suppressions per import request.
Export
GET /v1/suppressions/export
| Parameter | Type | Description |
|---|---|---|
format | string | Export format: "csv" or "json" (default: "json") |
curl "https://pushmail.dev/api/v1/suppressions/export?format=csv" \
-H "Authorization: Bearer pm_live_YOUR_KEY" \
-o suppressions.csvcurl "https://pushmail.dev/api/v1/suppressions/export?format=json" \
-H "Authorization: Bearer pm_live_YOUR_KEY"[
{
"id": 1,
"orgId": 5,
"email": "bounced@example.com",
"reason": "hard_bounce",
"source": "automatic",
"createdAt": "2025-03-01T10:00:00.000Z"
}
]The CSV export returns a file with columns: email, reason, source, created_at.
Automatic suppression
PushMail automatically adds emails to the suppression list when:
- Hard bounce -- SendGrid reports a permanent delivery failure
- Spam complaint -- A recipient marks your email as spam
- List unsubscribe -- A recipient uses the List-Unsubscribe header
- Dropped -- SendGrid drops the email due to previous bounces or spam reports
These automatic suppressions protect your sender reputation and deliverability. You can remove automatic suppressions via the API or dashboard, but exercise caution -- re-sending to previously bounced addresses can harm your reputation.
Send pipeline integration
When you send an email (transactional, campaign, or sequence), PushMail checks the suppression list at two points:
- API entry point (
POST /v1/send): Returns422with a clear error message if the recipient is suppressed - Queue consumer: Rejects the email with status
rejectedif the recipient was suppressed between queueing and processing
Suppressed sends do not consume credits.