# Suppressions and objections

> Upload suppression lists that apply to every count and order, check records before a send, and report an objection so it reaches every licensee holding the record.

Source: https://emailmovers.com/mydatahub/docs/suppressions
Updated: 4 October 2026

Suppression runs both ways. You upload your own lists, and an objection reported by anyone reaches every organisation that holds the record.

Lists are free, apply to every later count and order, and are stored as SHA-256 hashes only. Everyone has an absolute right to object to direct marketing, and it is kept by suppression, not erasure.

> **Not yet live.** MyDataHub v2 is built and tested locally and is not yet live. This page describes the contract the API is tested against, and the launch date is to confirm.

## Upload a list

`POST /v1/suppressions` takes a `name`, a `kind` and up to 100,000 `values`.

```json
{
  "name": "Existing customers",
  "kind": "email",
  "values": ["jo.example@example.co.uk", "sam@example.org"]
}
```

| `kind` | What the values are | How they are read |
| --- | --- | --- |
| `email` | Email addresses | Trimmed, lower cased and hashed |
| `domain` | Domains | Trimmed, lower cased and hashed. `*.acme.co.uk` is a suffix match, and a wildcard needs a domain, not `*.com` |
| `phone` | Telephone numbers | Read as E.164 digits: `0113 234 5671` becomes `441132345671` |
| `company_number` | Companies House numbers | Upper case and padded to 8 characters: `5046417` becomes `05046417` |
| `title_term` | Words in a job title | Kept as words, at most five, never hashed |

The normalisation follows the advertising platform convention, so an existing suppression file works unchanged. If you already hold SHA-256 hashes, send `hashed` true with 64 lower case hex characters per value.

The response tells you what it read. A repeated value is skipped, the first 100 skipped rows are named with a reason, and `rows_read` less `rows_valid` counts them all. A list with nothing valid is a 422.

```json
{
  "list": { "id": "sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA", "name": "Existing customers", "kind": "email", "row_count": 1198 },
  "rows_read": 1204,
  "rows_valid": 1198,
  "rows_skipped": [{ "row": 17, "reason": "Not an email address." }]
}
```

A suppression list is processed under the Article 28 data processing agreement, so your organisation accepts it before the first upload. A person who owns the account accepts it in the app's Settings, or with `POST /v1/account/agreements` and `{"dpa_accepted_version": "1.0"}`, the version `GET /v1/account` names in `agreements.dpa`. The app's first upload asks with a box of its own.

Until it is accepted, an upload answers 403 `dpa-acceptance-required` and nothing is uploaded. When a new version is in force, it is accepted again.

## Use a list in an audience

Name the list in an `in_list` filter, usually under `not`.

```json
{ "not": { "field": "email.address_sha256", "op": "in_list", "list_id": "sup_01JB4H7Q2V7XN3M9R6T1Y5W8ZA" } }
```

Each count reports what each list removed in `compliance_summary.suppressed_removed`, as `{list_id, removed}`. Title term and domain wildcard lists report the same way.

Suppressed records never appear in counts or orders, and a matched record is never charged. See [The audience definition](https://emailmovers.com/mydatahub/docs/audience.md).

## Check before you send

`POST /v1/suppressions/check` takes up to 1,000 `record_ids` and returns the status of each as of now. Run it before any send.

```json
{ "record_ids": ["contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8"] }
```

| `suppression_status` | Meaning |
| --- | --- |
| `none` | Not suppressed |
| `objected_to_emailmovers` | The person objected to Emailmovers, so the record is suppressed database-wide |
| `objected_to_licensee` | The person objected to an organisation that holds the record, and it is on that organisation's objections list |
| `deceased` | The person has died |
| `left_company` | The person has left the company |

Each result also says `suppressed` and which of your `list_ids` hold the record. A record the current dataset no longer holds is still found if it was objected to.

## Report an objection

`POST /v1/objections`, or `report_objection` over MCP, records an objection to direct marketing. Send exactly one of `record_id` or `email_sha256`, with the `channel` it arrived by, when it was `received_at` and a `scope`.

```json
{
  "record_id": "contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8",
  "channel": "email",
  "received_at": "2026-10-01T08:30:00Z",
  "scope": "emailmovers",
  "note": "Replied asking not to be contacted again."
}
```

| `scope` | Effect |
| --- | --- |
| `licensee` | Suppresses the record for your organisation at once. The response is `201` with status `suppressed` |
| `emailmovers` | Suppresses it database-wide. The response is `201` with status `pending` and an `effective_by` time of 17:00 UTC on the next working day |

The record goes on your objections list immediately for both scopes. A database-wide objection then reaches every licensee through a same-day overlay that is subtracted from every count, sample and order, and each organisation holding the record gets a signed `suppression.created` webhook.

The webhook carries the record, the reason, the scope and the time. It never says who reported it or what the note said.

Because a licensee must not be able to remove records from every other customer's counts unchecked, scope `emailmovers` has three conditions.

1. You must hold the record, from a delivered live order or an enrichment. Otherwise it is a 422 that advises scope `licensee`, which still protects the person for your organisation at once.
2. An organisation may report 200 a day. Beyond that the answer is a 429 with the same advice.
3. It costs one unit of your counts budget. See [Rate limits](https://emailmovers.com/mydatahub/docs/rate-limits.md).

## Pull instead of waiting

`GET /v1/suppressions?since=` returns the lists changed after a time, each with `added_since`, the notices for records added to it. Use it to catch up after downtime, alongside the `suppression.created` [webhook](https://emailmovers.com/mydatahub/docs/webhooks.md).

## What a delivered record looks like after an objection

A record objected to after delivery stays in your records pages and downloads, so counts, cursors and your CRM's own key still line up. Its email, telephone number and name are blank, and `compliance.suppression_status` says why.

A file downloaded after that no longer hashes to the `content_sha256` on the receipt, which records the day of delivery. See [Compliance receipts](https://emailmovers.com/mydatahub/docs/compliance-receipts.md).

## Deadlines and law

The right to object to direct marketing is absolute under UK GDPR, and an objection must be honoured within one month. Here it takes effect for your organisation at once, and database-wide within one working day. Telephone screening is separate: every telephone number is screened against the TPS and CTPS each night through a licensed screening service, and carries its status and the date of that screen, valid for 28 days.

## Next steps

- Check the objection and list endpoints in the [REST reference](https://emailmovers.com/mydatahub/docs/reference/rest.md).
- Read how receipts count suppressed records in [Compliance receipts](https://emailmovers.com/mydatahub/docs/compliance-receipts.md).
- Subscribe to `suppression.created` in [Webhooks](https://emailmovers.com/mydatahub/docs/webhooks.md).
