Skip to main content

Importing a list

Upload a file or sync from WinRed or Anedot. A list has one of four sources: A list is processing while the import runs and ready when it is done. You cannot 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.
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, and in the import summary over the API. The request takes no options: sending that field returns 400 VALIDATION_ERROR.

What import removes and holds back

Every import, from the uploader or the API, applies the same checks:
  • Malformed addresses are rejected and reported.
  • Likely typos in the domain are skipped, not corrected. A row whose domain is a known misspelling of a major mailbox provider, such as gmial.com, is counted in the import summary as “Likely typo in the domain”. Fix the address in your file and import it again.
  • Role addresses such as info@, admin@, and sales@ are always removed. They reach a shared inbox rather than a person and complain at a far higher rate than personal addresses, so there is no option to keep them.
  • Disposable addresses from throwaway-mailbox services are removed. The list we check covers several thousand such domains.
  • Addresses that cannot receive mail, such as a domain with no mail server, and addresses that already bounced or complained on this platform, are removed, as are duplicate rows in the same file.
  • Addresses currently held by screening are imported but held back, and the import summary reports them as screened. See Address screening at send time.
Suppressed addresses stay suppressed: re-importing a file does not bring back anyone who unsubscribed, bounced, or complained.

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). Giving is counted net of refunds: a refunded or charged-back gift does not count toward a total or a gift count, and a gift that arrives through both a webhook and a scheduled report is counted once. 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. 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. An acquired list is not blocked from sending. Declaring it has one consequence: warm-up runs on half steps. The daily ceiling is halved at every stage of the warm-up ramp, so a list with less certain provenance reaches mailbox providers more slowly.

Address screening at send time

There is no separate cleaning step and no charge for one. Every campaign email is screened as it is sent, using the strictest setting of the mail provider’s automatic validation. An address it rates as likely undeliverable is not sent to. What happens to a screened address:
  • The message is recorded with status suppressed and reason auto_validation.
  • It is not counted as a bounce, so it does not count toward your bounce rate or the deliverability breakers, and it does not fire the email.bounced webhook.
  • The address is held out of all sends on the platform for 90 days, for every organization. After 90 days it is checked again on its next send.
  • The campaign report shows the total as Screened as invalid.
  • You are billed the normal per-recipient email rate for it, the same as a bounce, because the message was handed to the mail provider.
Screening is a safeguard, not a guarantee. It does not confirm that a mailbox exists at the large providers, so an address that has never been seen before and is dead can still bounce once. A permanent bounce from a bad address suppresses it for good; see Bounces, complaints, and suppressions. A campaign sends to every subscribed contact on its lists that is not suppressed. There is no recipient policy to choose.

Exporting a list

Export on the list page downloads a plain contact export: the columns from the file you uploaded, in the order they arrived, followed by email, status, and status_reason. There are no verdict columns and no state filters.

Contact statuses

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.

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. 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. Opening or clicking one of your emails counts as engagement and starts that clock over.

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:
Fix and resend only the rows that came back rejected.