> ## Documentation Index
> Fetch the complete documentation index at: https://docs.politicalcomms.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Lists and Validation

> Importing email contacts, recording consent, paid list validation, segments, and the sunset policy for unengaged contacts.

## Importing a list

Upload a file or sync from WinRed or Anedot. A list has one of four sources:

| Source      | Where it comes from                                   |
| ----------- | ----------------------------------------------------- |
| `uploaded`  | A file you imported                                   |
| `segmented` | A segment built from contacts already in the platform |
| `winred`    | Synced from WinRed                                    |
| `anedot`    | Synced from Anedot                                    |

A list is `processing` while the import runs and `ready` when it is done. You
cannot validate or send to a list that is not `ready`; the API returns
`409 EMAIL_LIST_NOT_READY`.

### Where the uploader lives

Every file you upload to the platform now goes through one page: **Assets >
File Uploads**. Pick **Email recipients** as the kind and drop the file: the
file you upload *is* the list, named after the file unless you rename it, so
there is no list to create first. Name it, choose whether it is available to
every campaign or only to one sending domain, then map the columns and record
consent. Texting contacts, opt-out numbers, email unsubscribes, texting media,
and email images are the other kinds on the same page.

A campaign combines lists by attaching several of them, exactly as a texting
project attaches contact lists, so there is no reason to import two files into
one list.

The old **Email > Lists > Import** link still works and redirects to the
uploader with the kind already selected, so existing bookmarks are fine.

### Importing over the API

`POST /v1/email/lists/import` does the whole thing in one call: give it an
HTTPS URL to a CSV you host, and it fetches, stages, and commits it as a new
list. `name` defaults to the file name; `email_domain_id` scopes the list to one
sending domain, and omitting it leaves the list available to every campaign.

```json theme={null}
{
  "source_url": "https://files.example.com/exports/august-donors.csv",
  "name": "August donors",
  "consent": { "source": "donation_form", "note": "Donate page opt-in checkbox" }
}
```

`consent.source` is one of `donation_form`, `petition`, `signup_form`, `event`,
`purchased`, `rented`, or `other`. It is the same attestation the wizard asks
for, and the same reasons to answer honestly apply.

`mapping` is optional. Leave it out and the platform uses the mapping it
recognizes from the export's own headers, which is what you want when the file
came from a common ESP. If neither your mapping nor the recognizer finds an
email column, the call returns `400 VALIDATION_ERROR` and `details.headers`
lists the headers it read, so the retry can name the right column instead of
guessing.

The response is `202` with an import id; the import runs in the background and
its progress and row counts are shown on the list in the dashboard. Role
addresses such as `info@` and `sales@` are skipped
unless you send `options.allow_role`, because they complain at a far higher
rate than personal addresses.

### Donor syncs and segments

Two of the four sources are worth calling out because they are not files:

**WinRed and Anedot syncs** build an email list from your donor records
automatically, so people who give keep arriving on the list without anyone
exporting a CSV. Consent comes from the donation form itself, which is the
cleanest provenance an email list can have. Create one from **Email > Lists >
New dynamic list** (or from Contact Management); one connection feeds both a
text list and an email list, and the email list's page shows the connection
details and settings. The same picker offers **Recent openers** and **Recent
clickers**: lists of everyone who opened or clicked a campaign in a window you
choose, refreshed nightly. A refund can park a donor off the list, and a later gift brings them
back; retention works the same way. These lists are managed through the
connection rather than deleted, so the Delete action is not offered on them.

**Segments** are lists built from contacts already in the platform, using rules
rather than a file. Engagement rules are the useful ones: everyone who opened in
the last 30 days, everyone who clicked a particular campaign, everyone who has
not engaged in 90 days. Giving rules read your synced donations: total given,
recurring, gave recently, number of gifts, any single gift above an amount, and
the donation source (refcode, source code, UTM parameters, page name). A random
split rule takes a stable slice of a list, so two segments with 0-50 and 50-100
never overlap; that is how you run two campaigns against halves of one list.
Because a segment is defined by its rules, it reflects the current state of your
contacts each time you use it rather than a snapshot from the day you built it,
and it can refresh nightly.

## Consent attestation

Every list requires a consent attestation when you create it: a short statement
of **how** the people on it agreed to hear from you, plus an optional note.

This is not a checkbox to click past. It is the record you will want if a
complaint is ever escalated, and the honest answer determines what happens next.
A list from your own donate-page opt-in behaves differently from a list you
bought.

## Acquired lists

If a list came from anywhere other than your own sign-up flow, set its
`acquired` field. Doing so is in your interest, not against it: an acquired list
that is not declared is far more likely to trip a deliverability breaker
mid-send and pause your campaign.

Declaring a list acquired has three consequences:

1. **Paid validation is required before the first send.** Undeclared acquired
   lists are where bounce rates come from.
2. **Warm-up runs on half steps.** The daily ceiling is halved at every stage of
   the [warm-up ramp](/help/email/campaigns-and-deliverability#warm-up).
3. **Validation goes stale after 90 days**, and the sender scorecard flags it.

## Paid validation

Validation checks each address before you spend a send on it. It costs **\$0.02
per address**, and you are only charged when you run it. A 100,000-address list
finishes in tens of minutes, not hours.

Sending to a list you have not cleaned is how a campaign trips a bounce breaker
on its first run. Validation is the cheap way to find that out before the
mailbox providers do: at \$0.02 an address it costs less to check a questionable
list than to damage a domain's reputation sending to it.

### What the verdicts mean

Every address comes back in one of four states:

| Verdict           | What it means                                         | What to do                                                                                |
| ----------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| **Deliverable**   | The mailbox exists and accepts mail.                  | Send to it.                                                                               |
| **Undeliverable** | It does not exist, or the domain does not.            | Do not send. These are marked and excluded automatically.                                 |
| **Risky**         | It resolves, but something about it predicts trouble. | Judgment call. See below.                                                                 |
| **Unknown**       | The receiving server would not say either way.        | Usually safe in small numbers. A list that is mostly unknown is a list worth questioning. |

Each verdict carries a reason code:

| Reason               | Verdict       | Meaning                                                                                                                                        |
| -------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `ok`                 | Deliverable   | The mailbox was confirmed.                                                                                                                     |
| `invalid_syntax`     | Undeliverable | The address is not a valid email format.                                                                                                       |
| `no_mx`              | Undeliverable | The domain has no mail server.                                                                                                                 |
| `mailbox_missing`    | Undeliverable | The domain exists, but the mailbox does not.                                                                                                   |
| `platform_bounce`    | Undeliverable | Your own sending history recorded a hard bounce for this address in the last 90 days.                                                          |
| `accept_all`         | Risky         | The domain accepts mail for every address, so nobody can tell a real mailbox from a typo. Common at small organizations and universities.      |
| `disposable`         | Risky         | A throwaway address from a temporary-mailbox service. It was created to be abandoned.                                                          |
| `role_address`       | Risky         | `info@`, `donate@`, `press@`. These reach a shared inbox rather than a person, and they complain at a far higher rate than personal addresses. |
| `low_quality`        | Risky         | The address shows other patterns that predict low engagement or high complaint rates.                                                          |
| `low_deliverability` | Risky         | The mailbox provider is known to accept mail it will not actually deliver to the inbox.                                                        |
| `no_connect`         | Unknown       | The receiving mail server could not be reached.                                                                                                |
| `timeout`            | Unknown       | The receiving mail server did not answer in time.                                                                                              |
| `unavailable_smtp`   | Unknown       | The receiving mail server refused to confirm or deny the mailbox.                                                                              |
| `provider_missing`   | Unknown       | The mailbox provider could not be identified.                                                                                                  |
| `unexpected_error`   | Unknown       | The check failed for a reason outside the above.                                                                                               |

`accept_all` domains are now actually probed rather than assumed deliverable,
so a catch-all address lands in **risky** with reason `accept_all` instead of
showing as deliverable.

A cautious sender excludes risky addresses. A sender with a genuinely
well-collected list may keep accept-all and drop the rest. Disposable
addresses are worth dropping in every case.

### What a run charges

A run is billed for every address it checks, at the per-address price, before
the check starts; the count you are quoted is the count you pay for. A
duplicate address within the same list is only ever charged once. An address
that comes back **unknown** is charged like any other, is not held as an
answer, and is checked again the next time you run validation. If a run stops
before it delivers its verdicts, the addresses it did not reach are refunded.

Verdicts **go stale after 90 days**. An acquired list validated four months
ago is treated as unvalidated, and the sender scorecard flags it.

Your own delivery data feeds the results. A hard bounce on any campaign marks
the address undeliverable (`platform_bounce`) for the next 90 days, and a
successful delivery keeps an existing deliverable verdict current, so a
re-validation after a send reflects what your own sending already learned.

### Large runs

A run on a large list proceeds in chunks rather than all at once, so progress is
never lost to a single failure. The status tells you where it is:

| Status                        | Meaning                                                                  |
| ----------------------------- | ------------------------------------------------------------------------ |
| `queued`                      | Waiting to start.                                                        |
| `processing`                  | Running.                                                                 |
| `paused_insufficient_balance` | Your wallet ran out mid-run. Add funds and it picks up where it stopped. |
| `completed`                   | Done.                                                                    |
| `failed`                      | Stopped on an error.                                                     |

**A run paused for balance is not a lost run.** Everything already validated
stays validated and stays paid for; topping up resumes it from that point rather
than starting over.

Start a run from the list page. Progress is shown on the list while it runs,
and only one run per list goes at a time. Validation is a paid, dashboard-driven
workflow, so it is not part of the public API.

Addresses that fail validation are marked `invalid` and are not sent to.

### Exporting the results

Once a run completes, **Export results** on the list page downloads a CSV of
what validation found: the columns from the file you uploaded, in the order they
arrived, followed by the address, its list status, and every verdict field
behind it (state, reason, a 0-100 quality score, syntax, DNS, mailbox, role,
disposable, free, accept-all, did-you-mean, MX record, SMTP provider, platform
bounce count, and when it was validated). The filter next to the button picks
what the file contains:

| Filter                 | Includes                                                                                                                    |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| Maximum Deliverability | Deliverable addresses only                                                                                                  |
| Maximum Reach          | Deliverable, risky and unknown addresses, minus disposable and role mailboxes. The most common choice for sending           |
| Only Bad Emails        | Undeliverable addresses plus disposable and role mailboxes. The ones to avoid, or to clean out of your own system of record |
| Custom                 | One verdict class (deliverable, undeliverable, risky, unknown), or every contact                                            |

The three presets describe verdicts, so an address that was never validated is
outside all of them; "every contact" is the one filter that includes it.

The `score` column is a 0-100 quality signal and sits right after the reason
column; it is blank for verdicts we derive from your own sending history
(`platform_bounce` and deliverable addresses refreshed by a delivery), since
those are certainties rather than scored estimates. `mx_record`,
`smtp_provider`, and `did_you_mean` are populated whenever the check reached
that stage.

Verdict columns are **blank rather than `false`** when an address has no cached
verdict. That happens when the address was never validated, or when its verdict
aged past the 90-day window. Blank means "no answer", which is not the same as
"no".

## Contact statuses

| Status         | Meaning                                        |
| -------------- | ---------------------------------------------- |
| `subscribed`   | Sendable.                                      |
| `unsubscribed` | Opted out. Never sent to again.                |
| `bounced`      | Permanently bounced.                           |
| `complained`   | Marked a message as spam. Never sent to again. |
| `invalid`      | Failed validation.                             |
| `sunset`       | Dropped by the sunset policy.                  |

Only `subscribed` contacts are mailed. The rest are excluded automatically, and
none of them can be restored by re-importing the same file.

## Removing contacts

Removing an address **unsubscribes it rather than deleting the row**, because the
row carries the bounce and complaint history that makes suppression survive your
next import. To erase an address entirely, use
[forget this address](/help/email/compliance-and-link-tagging#forget-this-address).

## Suppressions and the sunset policy

Suppressions, bounces, complaints, unsubscribes, and the 90-day hold and 180-day
sunset all work the same way regardless of which list an address is on, so they
have their own page:
[Bounces, complaints, and suppressions](/help/email/bounces-complaints-and-suppressions).

The short version: an address that bounces permanently or complains is
suppressed for you automatically, unsubscribes are honored immediately, and
contacts who have not engaged in 90 days stop being mailed before they can
damage your reputation.

## Bulk contact upsert over the API

`POST /v1/email/lists/{id}/contacts` takes up to 1,000 contacts per call and
returns a per-row outcome, so one bad address does not cost you the batch:

```json theme={null}
{
  "written": 2,
  "accepted": 2,
  "rejected": 1,
  "duplicates": 1,
  "results": [
    { "index": 0, "email": "a@example.com", "status": "accepted", "reason": null },
    { "index": 1, "email": "nope", "status": "rejected", "reason": "invalid_email" },
    { "index": 2, "email": "a@example.com", "status": "duplicate", "reason": "duplicate_in_request" }
  ]
}
```

Fix and resend only the rows that came back `rejected`.
