---
title: Suppressions
description: How addresses land on your suppression list, what happens when you send to them, and how to list, add, and remove entries.
---

> **For AI agents:** the complete documentation index is at [llms.txt](/docs/llms.txt). Append `.md` to any page URL for its markdown version.

A suppression is an address Mailfully will not deliver to. Entries are created automatically from hard bounces and spam complaints, or manually. The list is org-global: one list covers both your test and live environments.

## What gets suppressed automatically

| Trigger | Reason recorded |
|---|---|
| Permanent (hard) bounce | `hard_bounce` |
| Spam complaint | `complaint` |

Transient and undetermined bounces **never** suppress. A soft bounce adds no entry, changes no message status, and emits no webhook. Mailfully stores every hard-bounced or complained address lowercased and mirrors each entry to the underlying SES account-level suppression list.

## Sending to a suppressed address

Sends to suppressed addresses are **not rejected**. You get no error. At accept time, suppressed recipients are silently dropped from `to`, `cc`, and `bcc`:

- **Some recipients suppressed**: the message sends to the remaining recipients.
- **All `to` recipients suppressed**: you still get `202` and a message id, but the message is persisted with status `canceled`. Nothing is sent, and the message is never metered.

A race window also exists: an address can be suppressed between accept and send (for example, by a hard bounce landing moments earlier). A pre-send guard re-checks every recipient immediately before dispatch. If all `to` recipients are suppressed by then, the message flips to status `failed` with `last_event: "suppressed"`, and a synthetic `suppressed` event listing every dropped recipient is written to the message's event timeline.

<Warning>
A `202` means Mailfully accepted the request, not that mail went out. If you need to know whether a message was actually sent, poll [`GET /v1/emails/:id`](/api-reference/emails/get) for its status, or subscribe to [webhooks](/guides/webhooks).
</Warning>

## List and filter suppressions

Listing requires the `read:suppressions` scope, which can be minted onto an API key. An `mf_` key works here.

<Tabs>
<Tab title="curl">

```bash
curl "https://api.mailfully.com/v1/suppressions?email=ada@example.com" \
  -H "Authorization: Bearer mf_live_xxxxxxxxxxxx"
```

</Tab>
<Tab title="Node SDK">

```typescript
import { Mailfully } from "@mailfully/node";

const mailfully = new Mailfully({ apiKey: process.env.MAILFULLY_API_KEY ?? "" });

const { data, error } = await mailfully.suppressions.list({
  email: "ada@example.com",
});
```

</Tab>
</Tabs>

```json
{
  "data": [
    {
      "id": "01J1F5S8ZG9Q2W7X4V3B2N1M0K",
      "email": "ada@example.com",
      "reason": "hard_bounce",
      "source": "ses_notification",
      "created_at": "2026-07-08T14:23:07.123Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

Query parameters:

- `email`: exact match. Stored values are canonical lowercase, so lowercase your query or it will not match.
- `reason`: exact match against `hard_bounce`, `complaint`, or `manual`.
- `limit`: default 25, max 100.
- `cursor`: opaque cursor from a previous page's `next_cursor`.

See [List suppressions](/api-reference/suppressions/list) for the full schema.

## Add a suppression manually

```bash
curl -X POST https://api.mailfully.com/v1/suppressions \
  -H "Authorization: Bearer <session token>" \
  -H "Content-Type: application/json" \
  -d '{ "email": "ada@example.com" }'
```

```json
{
  "id": "01J1G0Q4RS7T9V2W5X8Y1Z3A6B",
  "email": "ada@example.com",
  "reason": "manual",
  "source": "dashboard",
  "created_at": "2026-07-08T15:01:44.502Z"
}
```

<Note>
Adding and removing entries needs the session-only `manage:suppressions` scope, so an `mf_` key gets `403 insufficient_scope` here. See [Authentication](/concepts/authentication).
</Note>

The add is idempotent: a new entry returns `201`, and adding an address that is already suppressed returns `200` with the existing entry. The email is trimmed and lowercased before storing. See [Create suppression](/api-reference/suppressions/create).

## Remove a suppression

```bash
curl -X DELETE https://api.mailfully.com/v1/suppressions/01J1F5S8ZG9Q2W7X4V3B2N1M0K \
  -H "Authorization: Bearer <session token>"
```

```json
{
  "id": "01J1F5S8ZG9Q2W7X4V3B2N1M0K",
  "deleted": true
}
```

When the entry was synced to SES, Mailfully also clears the SES-level block if no other suppression still covers that address, then deletes the entry. The call fails closed: if SES cannot be updated, the request returns `503 service_unavailable` and the entry stays intact. Retry the delete. See [Delete suppression](/api-reference/suppressions/delete).

<Warning>
Think twice before removing a `hard_bounce` entry. The address permanently rejected your mail; sending to it again will most likely hard-bounce again, and the next permanent bounce re-suppresses it. Remove hard-bounce entries only when you know the mailbox has changed — a typo fix, a re-created account.
</Warning>

## Reason values

| Reason | Written by | Source value |
|---|---|---|
| `hard_bounce` | Permanent bounce | `ses_notification` |
| `complaint` | Spam complaint | `ses_notification` |
| `manual` | `POST /v1/suppressions` | `dashboard` |
