Each stream of a server keeps its own list of addresses it will not send to. If any recipient of a message is suppressed, Sendora refuses the whole message with recipient_suppressed and sends it to no one. The error lists the suppressed addresses and the stream. A broadcast instead drops suppressed addresses and sends to the rest.
The calls on this page take streamId, and without it they mean the server’s default transactional stream.
A receiver refused the address for good, for instance because the mailbox does not exist. The bounced webhook tells you which message.
You
spam_complaint
The recipient reported a message as spam, and the receiver told Sendora through its feedback loop. The spam_complaint webhook tells you.
Sendora support only
unsubscribe
The recipient used the unsubscribe link of a message on a broadcast stream, or their mail client’s one-click button. The unsubscribedwebhook tells you.
Sendora support only
manual
You added the address in the dashboard. The API cannot add one.
You
After one hard bounce, every later send to that recipient on the stream fails with recipient_suppressed. A soft bounce or a deferral never suppresses a recipient.
import os
from sendora import Sendora
sendora = Sendora(os.environ["SENDORA_API_TOKEN"])
page = sendora.suppressions.list(limit=100)
for entry in page.suppressions:
print(entry.address, entry.reason, entry.created_at)
The list is newest first, in the page sizes on Limits. Pass next as after for the next page. Each entry has the streamId of its list. To read another stream’s list, pass that stream’s streamId in the query. A stream of another server fails with stream_not_found.
Each stream’s Suppressions tab, under its server’s Streams tab in Servers, shows the list. There you add addresses, and lift the entries you may lift.
Lift a hard bounce or a manual entry when the recipient can take mail again, for instance once they have confirmed a corrected mailbox. The address travels in the request body, never in a URL. streamId in the body names the stream, and without it the default one.
import os
from sendora import Sendora
sendora = Sendora(os.environ["SENDORA_API_TOKEN"])
sendora.suppressions.delete(address="bounced@example.com")
Lifting a spam complaint or the recipient’s own unsubscribe fails with spam_complaint_locked or unsubscribe_locked. To have one lifted, write to support@sendora.se with the recipient’s own request and the entry’s streamId and messageId from the list. Leave the address out, since the stream and the message are enough to find the entry.
When a recipient asks you to erase their data, erase them with an account key. Every message to or from the address, sent and received, loses its content, and the address becomes a pseudonym in the log of every server.
The erasure returns pending, and Sendora carries it out within a minute. Read the erasure to see it done, with the pseudonym and how many messages lost their content.
On the suppression lists, a hard bounce is removed. A spam complaint, an unsubscribe or a manual entry keeps only a keyed hash of the address, so the recipient stays protected from further mail without the address being stored. An erasure blocks nothing else: later mail to or from the address is sent and received as before.
# Suppressions
Addresses a stream no longer sends to, why they are there, and how to lift one.
Each [stream](https://sendora.se/docs/streams) of a server keeps its own list of addresses it will not send to. If any recipient of a message is suppressed, Sendora refuses the whole message with `recipient_suppressed` and sends it to no one. The error lists the suppressed addresses and the stream. A [broadcast](https://sendora.se/docs/broadcasts) instead drops suppressed addresses and sends to the rest.
The calls on this page take `streamId`, and without it they mean the server's default transactional stream.
## How an address gets there
| Reason | Added when | Who can lift it |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------- |
| `hard_bounce` | A receiver refused the address for good, for instance because the mailbox does not exist. The `bounced` webhook tells you which message. | You |
| `spam_complaint` | The recipient reported a message as spam, and the receiver told Sendora through its feedback loop. The `spam_complaint` webhook tells you. | Sendora support only |
| `unsubscribe` | The recipient used the unsubscribe link of a message on a [broadcast stream](https://sendora.se/docs/broadcasts), or their mail client's one-click button. The `unsubscribed` [webhook](https://sendora.se/docs/webhooks) tells you. | Sendora support only |
| `manual` | You added the address in the dashboard. The API cannot add one. | You |
After one hard bounce, every later send to that recipient on the stream fails with `recipient_suppressed`. A soft bounce or a deferral never suppresses a recipient.
## Read the list
cURL:
```bash
curl 'https://api.sendora.se/v1/suppressions?limit=100' \
-H "Authorization: Bearer $SENDORA_API_TOKEN"
```
TypeScript:
```ts
import { Sendora } from '@sendora/sdk';
const sendora = new Sendora({
token: process.env.SENDORA_API_TOKEN,
});
const { suppressions } = await sendora.suppressions.list({
limit: 100,
});
for (const entry of suppressions) {
console.log(
entry.address,
entry.reason,
entry.createdAt,
);
}
```
Python:
```python
import os
from sendora import Sendora
sendora = Sendora(os.environ["SENDORA_API_TOKEN"])
page = sendora.suppressions.list(limit=100)
for entry in page.suppressions:
print(entry.address, entry.reason, entry.created_at)
```
Response:
```json
{
"suppressions": [
{
"streamId": "3d1a2b4c-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"address": "bounced@example.com",
"reason": "hard_bounce",
"messageId": "5f432ffd-c005-499b-aa3a-5b8a088ea20e",
"createdAt": "2026-09-14T12:00:05.000Z"
}
],
"next": null
}
```
The list is newest first, in the page sizes on [Limits](https://sendora.se/docs/limits#page-sizes). Pass `next` as `after` for the next page. Each entry has the `streamId` of its list. To read another stream's list, pass that stream's `streamId` in the query. A stream of another server fails with `stream_not_found`.
Each stream's **Suppressions** tab, under its server's Streams tab in [Servers](https://app.sendora.se/servers), shows the list. There you add addresses, and lift the entries you may lift.
## Lift a suppression
Lift a hard bounce or a manual entry when the recipient can take mail again, for instance once they have confirmed a corrected mailbox. The address travels in the request body, never in a URL. `streamId` in the body names the stream, and without it the default one.
cURL:
```bash
curl -X POST https://api.sendora.se/v1/suppressions/delete \
-H "Authorization: Bearer $SENDORA_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"address": "bounced@example.com"
}'
```
TypeScript:
```ts
import { Sendora } from '@sendora/sdk';
const sendora = new Sendora({
token: process.env.SENDORA_API_TOKEN,
});
await sendora.suppressions.delete({
address: 'bounced@example.com',
});
```
Python:
```python
import os
from sendora import Sendora
sendora = Sendora(os.environ["SENDORA_API_TOKEN"])
sendora.suppressions.delete(address="bounced@example.com")
```
Lifting a spam complaint or the recipient's own unsubscribe fails with `spam_complaint_locked` or `unsubscribe_locked`. To have one lifted, write to [support@sendora.se](mailto:support@sendora.se) with the recipient's own request and the entry's `streamId` and `messageId` from the list. Leave the address out, since the stream and the message are enough to find the entry.
## Erasure requests
When a recipient asks you to erase their data, erase them with an account key. Every message to or from the address, sent and received, loses its content, and the address becomes a pseudonym in the log of every server.
cURL:
```bash
curl -X POST https://api.sendora.se/v1/erasures \
-H "Authorization: Bearer $SENDORA_ACCOUNT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"address": "anna@example.com"
}'
```
TypeScript:
```ts
import { SendoraAccount } from '@sendora/sdk';
const account = new SendoraAccount({
token: process.env.SENDORA_ACCOUNT_TOKEN,
});
const erasure = await account.erasures.create({
address: 'anna@example.com',
});
console.log(erasure.erasureId, erasure.status);
```
Python:
```python
import os
from sendora import SendoraAccount
account = SendoraAccount(
os.environ["SENDORA_ACCOUNT_TOKEN"]
)
erasure = account.erasures.create(
address="anna@example.com"
)
print(erasure.erasure_id, erasure.status)
```
Response:
```json
{
"erasureId": "6f1d2c3b-4a5e-4f60-8b7a-9c8d7e6f5a41",
"status": "pending",
"createdAt": "2026-10-04T12:00:00.000Z",
"completedAt": null,
"pseudonym": null,
"messages": null,
"receivedMessages": null
}
```
The erasure returns `pending`, and Sendora carries it out within a minute. [Read the erasure](https://sendora.se/docs/api/get-v1-erasures-id) to see it `done`, with the pseudonym and how many messages lost their content.
On the suppression lists, a hard bounce is removed. A spam complaint, an unsubscribe or a manual entry keeps only a keyed hash of the address, so the recipient stays protected from further mail without the address being stored. An erasure blocks nothing else: later mail to or from the address is sent and received as before.