# Manage your email contacts, lists and segments

> Add, find, tag, export and erase contacts, organize them into lists, custom fields and segments, and import an audience from a file or EmailOctopus.

Source: https://www.coritan.com/docs/organizations/commerce/email-contacts/

In the dashboard:

- /dashboard/organizations/…/commerce/contacts: https://www.coritan.com/dashboard/organizations
- /dashboard/organizations/…/commerce/lists: https://www.coritan.com/dashboard/organizations

**Contacts** lists the people you can email, **Lists** holds the lists they join with the custom fields and tags that describe them, and **Segments** and **Import** are two views of **Contacts**. A contact is an email address with a status, the lists it is on, and the record of the consent it gave.

## Before you begin

- You need Tier 1 support or higher to read contacts, lists, fields, tags and segments, and Tier 3 support or higher to change them. **Import** opens for Tier 3 support and above only.
- Your store needs no setup to hold contacts, but nobody is emailed until the [sending domain](/docs/organizations/commerce/email-sending/) is ready.
- Contacts belong to a mode. A store in test mode makes test contacts, and a live store makes live contacts. The list shows both, and a campaign mails the contacts of its own mode ([Set up email marketing for your store](/docs/organizations/commerce/email-marketing/#rules-that-hold-everywhere)).

## How contacts get on your list

A contact comes from one of these, and **Source** says which:

| Source | How the contact got there |
| --- | --- |
| `form:` and a number, such as `form:3` | A [signup form](/docs/organizations/commerce/email-forms/), whether it is hosted, embedded or posted to the Store API. The number is the form's. |
| **Popup**, or another word your storefront chose | A lead who ticked the consent box on your storefront's popup or sign-up form ([Leads](/docs/organizations/commerce/leads/)). The source is the lead's own, such as `popup` or `footer`. The lead joins your default list as subscribed, with the words it agreed to. |
| **Checkout** | A shopper who ticked the marketing box at checkout. |
| **Account page** | A shopper who turned on marketing in their storefront account. |
| **Import** and **EmailOctopus** | A file or an EmailOctopus account. |
| **Added by hand** | **Add contact…** in the dashboard. |
| **API** | A call to `POST /marketing/contacts`, or to the Store API's signup call with no `source` and no form. |

The **Any source** filter lists the named sources only, so it does not find a contact whose source is `form:3`. A shopper who turns marketing off in their account, or unsubscribes from any store email, becomes **Unsubscribed** here too. The two stay in line.

## Find a contact

1. In the [dashboard](https://www.coritan.com/dashboard/organizations), open the organization, then **Commerce**, then **Email**, then **Contacts**.
2. Type part of a name or an address in **Search by name or address**.
3. Narrow the list with **Any status**, **Every list**, **Any tag**, **Any segment** and **Any source**. The statuses are **Subscribed**, **Pending**, **Unsubscribed**, **Bounced** and **Marked as spam**.
4. Choose how to sort under **Sort by**: **Newest first**, **Address, A to Z** or **Most engaged first**.

The list shows 50 contacts a page, with the **Contact**, its **Status**, **Tags**, **Lists**, **Engagement**, **Source** and the date it was **Added**. Every filter is kept in the page address, so a reload or a shared link opens the same list. The count above the list says how many contacts match.

## Add a contact

Add a person only when you hold their consent already.

1. On **Contacts**, select **Add contact…**.
2. Enter the **Email**, and optionally the **First name** and **Last name**.
3. Under **Consent**, choose **Subscribed** or **Pending**. **Subscribed** adds the person to the lists you tick at once. **Pending** emails them a confirmation link, and they join the lists when they click it.
4. Under **Words they agreed to**, write what they agreed to. It is kept as the record of consent. Leave it empty to record that you added them.
5. Tick the **Lists** and enter any **Tags**, separated by commas, then select **Add contact**.

With no list ticked, the contact joins the default list. An address that is already a contact answers **This address is already a contact**, with a link to it. An address on the suppression list is added with the status it is held under, and nothing is ever mailed to it.

## Open a contact

Select a contact to open its page. **Profile** holds the names, the email address and your custom fields, and **Save changes** keeps an edit. The other cards are:

**Timeline**
: What happened to the contact, newest first: signups and confirmations, emails sent, delivered, delayed or failed, opens, clicks, bounces, spam reports, unsubscribes, orders, lists joined and left, and consent. A click names the campaign and the link. **Show older** pages back. An open or a click made by a mail privacy tool is marked automatic.

**Consent**
: The words the contact agreed to, when and how, and where the contact came from. When the contact is not subscribed, it also shows when and why it stopped. A contact with no consent on record shows a warning, so check how it was added before you email it.

**Lists** and **Tags**
: The lists the contact is on, with **Add to list…** for a subscribed contact, and its tags, with **Add tag**.

**Engagement**
: The *engagement score*, a figure from 0 to 100 for how often the contact opens and clicks. Opens add up to 40 points and clicks up to 60, with ten opens and five clicks giving the most, and both fade to nothing over 90 days. Opens by mail privacy tools do not count. The card also shows emails received, opens, clicks, and when the last email, open and click were.

On a contact that is **Subscribed** or **Pending**, **Unsubscribe…** takes it off every list. That is your own decision for the contact, so only the contact's own signup brings it back. No control in the dashboard resubscribes anyone.

A contact that bounced or marked an email as spam shows an alert. Its address is on the suppression list, and no campaign or automation sends to it. Nobody can lift that.

## Change many contacts at once

1. On **Contacts**, tick the contacts, or tick the box in the header to pick the page. To pick every contact the filters match, select the **Select all** link that names how many match.
2. Select **Bulk actions**.
3. Choose **Add a tag…**, **Remove a tag…**, **Add to a list…**, **Remove from a list…**, **Unsubscribe…** or **Delete…**.

Only subscribed contacts join a list. A pending, unsubscribed or bounced contact is skipped. **Delete…** asks you to type `delete` and can also erase, as the next section says. One action changes at most 100,000 contacts, or 10,000 for **Unsubscribe…** and **Delete…**. Narrow the filters when you reach the limit.

## Export contacts and answer a data request

**Export CSV** downloads every contact the filters match, up to 100,000. With no filter set, it exports your whole audience. The columns are `id`, `email`, `first_name`, `last_name`, `status`, `tags`, `lists`, `source`, `consent_text`, `consent_at`, `consent_method`, `subscribed_at`, `unsubscribed_at`, `unsubscribe_reason`, `livemode`, `created_at` and one `fields.<key>` column for each custom field. A file with these headers imports back. A text cell that starts with `=`, `+`, `-` or `@` starts with an apostrophe, so a spreadsheet shows it as text. Each export is recorded in the [audit log](/docs/organizations/audit-log/).

To answer a person who asks what you know about them, open their contact, open its actions menu (**Contact actions**) and choose **Download data (JSON)**. The file holds the contact and its consent, tags, lists, events, emails received, its suppression entry, and the lead and customer with the same address.

To remove a person, open the same menu and choose one of:

- **Delete contact…** removes the contact, its list memberships and its tags. The emails it received stay in their campaigns' reports.
- **Erase contact…** also removes its events and its address from those emails.

Both ask you to type the contact's address, and neither can be undone. The suppression entry stays after either, so an address that unsubscribed is never mailed again.

## Consent and double opt-in

*Consent* is the record of what a person agreed to. A contact is subscribed only after it agreed, and the record stays after it unsubscribes. [Collect signups with forms](/docs/organizations/commerce/email-forms/#choose-lists-tags-and-consent) covers the words a form shows.

A list can ask for *double opt-in*: the person gets an email with a link and joins the list only when they click it. That proves the address is theirs, and it is the safest way to collect signups. Until they click, the contact is **Pending** and gets no campaign. The link works for 7 days, and the email is the `Confirm subscription` email ([Write the emails your store sends](/docs/organizations/commerce/emails/)). A person who signs up again while waiting gets a new link only after 10 minutes.

A signup from an address that unsubscribed, bounced or marked an email as spam changes nothing, and the form shows its usual thank you message, so a form never tells a visitor whether the store knows an address.

An unsubscribed contact can come back only on its own: from the page it reaches after unsubscribing, from its preference center, or by turning marketing on in its storefront account. An address that bounced or marked an email as spam cannot come back.

## Lists

1. Open the organization, then **Commerce**, then **Email**, then **Lists**.
2. Select **New list…**, enter a **Name** and optionally a **Description**, tick **New members confirm by email first** if the list needs a confirmation click, then select **Create list**.

**Lists** shows each list with its **Subscribed**, **Pending** and **Unsubscribed** counts and the joins of the last 14 days. **Active** and **Archived** switch the view. The first list a store makes becomes the default list, called **Newsletter** when the store makes it for you. A signup that names no list goes to the default list.

Select a list to open it. The page shows **Growth** for 14, 30 or 90 days, the **Members** (newest first, with the date a member left), and **Settings**:

| Setting | What it does |
| --- | --- |
| **Name** and **Description** | The description shows on the preference page when the list is public. |
| **Confirm new members by email** | Turns on double opt-in. **Confirmation subject** and **Confirmation message** replace the default words. The message is plain text, and `{{ confirm_url }}` is where the link goes. |
| **Send a welcome email** | Sends **Welcome subject** and **Welcome message** to each new member as soon as they join. The message is plain text, and merge tags such as `{{ first_name }}` work. This is the `Welcome to a list` email. |
| **Offer on the preference page** | Lets contacts join or leave the list themselves from the link in every email. |
| **Make this the default list** | Makes this list the one a signup without a list goes to. The old default stops being it. |

Select **Save changes** after an edit. **Archive list…** hides a list from signups, the preference page and the campaign picker, and keeps its members and history. **Restore list** brings it back. **Delete list…** removes a list nobody was ever on. For a list with members, the dialog offers to archive it instead. The default list can be neither archived nor deleted: make another list the default first. To take one contact off a list, use **Remove from list…** on the contact or on the list's **Members**. The contact stays on its other lists.

## Custom fields

A custom field holds something else you know about a contact, such as a company or a birthday. On **Lists**, the **Custom fields** card lists them. Select **Add field…** and fill in:

| Field | What it takes |
| --- | --- |
| **Label** | The name people see, up to 100 characters. |
| **Key** | The name used in merge tags as `{{ fields.key }}`. A letter first, then up to 31 letters, digits or underscores. `email`, `first_name` and `last_name` are taken. |
| **Type** | **Text**, **Number**, **Date**, **Yes or no**, or **One of several choices**. |
| **Choices** | For a choice field, one choice on each line, up to 50. |
| **Default value** | Used when a contact has no value for the field. |

The key and the type cannot change after the field exists. A store can have up to 100 custom fields. **Delete field…** asks you to type the key and removes the value from every contact that holds one. Merge tags and segment rules that use it stop matching.

## Tags

A tag is a free label, up to 64 characters, that you put on contacts to find them again or to start an automation. On **Lists**, the **Tags** card lists them with how many contacts carry each, and **Add tag…** makes one with a **Name** and a **Colour** that only you see. A tag is also created the first time you put it on a contact. A contact can carry up to 100 tags. **Delete tag…** takes the tag off every contact that carries it.

## Segments

A *segment* is a saved set of rules that picks contacts. The people it holds change as contacts change, so a campaign sent tomorrow uses tomorrow's matches.

1. On **Contacts**, choose **Segments**, then select **New segment…**.
2. Enter a **Name**, and optionally a **Description**.
3. Under **Rules**, the first group reads "Contacts match", a choice of **all** or **any**, and "of these rules". Choose one, then select **Add rule** and pick a field, an operator and a value.
4. Select **Add group** to nest a group of rules inside. Groups nest up to three levels deep, and a segment has at most 50 rules.
5. Watch **Who matches**. It counts the contacts the rules match after a short pause, shows a sample of the newest ten, and says **contacts match**. Select **Create segment**.

The count includes contacts of every status, whatever the rules say about status. A campaign sends only to the subscribed ones among them. To count only people you can email, add the rule `Status` `is` `subscribed`.

The segment list shows each segment's **Contacts** count as it was when last counted. **Count again** counts it now. **See the contacts** opens **Contacts** filtered to the segment. **Edit rules** opens the builder. **Delete segment…** asks you to type its name. A segment that a scheduled, sending or paused campaign, or an automation that is on, still uses cannot be deleted. The message names where it is used.

### The rule fields

Each rule is a field, an operator and a value. The builder groups the fields:

| Group | Fields |
| --- | --- |
| Contact | `Email`, `First name`, `Last name`, `Status`, `Source`, `Created`, `Subscribed`, `Tag` (has the tag, does not have the tag), `List` (is on, is not on: a subscribed membership), and each custom field by its label. |
| Engagement | `Emails received`, `Opens`, `Clicks`, `Engagement score`, `Last open`, `Last click`, `Last email sent`, `Opened a campaign`, `Clicked a campaign`, `Received a campaign`, and `Clicked a link` (a link containing the text you give). |
| Orders | `Number of orders`, `Total spent`, `Average order`, `First order`, `Last order`, `Bought a product`, `Bought a variant`, `Used a promo code`, `Customer group`, `Country` (the shipping or billing country, as a two-letter code) and `Is a customer`. |

The operators follow the field's type:

| Type | Operators |
| --- | --- |
| Text | is, is not, contains, does not contain, starts with, is set, is not set. Text matches without regard to case. |
| Number | equals, does not equal, is more than, is at least, is less than, is at most. |
| Date | is in the last (days), is not in the last (days), is before, is after, is set, is not set. A date without a time is a whole day. |
| Yes or no | is yes, is no. |
| Choice | is, is not, is one of. |

- Order facts match a contact to its orders by customer or by email address, in the contact's own mode, and leave cancelled orders out. `Total spent` and `Average order` are in the smallest unit of the order's currency, such as cents, after refunds.
- `Opened a campaign` and `Clicked a campaign` leave out opens and clicks by mail privacy tools and link scanners.
- `Received a campaign` means the email reached the contact's server.

## Import contacts

**Import** brings an audience in from a file or from EmailOctopus. Both end in a *dry run*, a trial that reads everything and counts what would happen without adding or changing a single contact. Only then can you run the import for real.

1. On **Contacts**, choose **Import**. Or select **More contact actions**, then **Import contacts…**.
2. Choose **CSV file** or **EmailOctopus**.

An import never subscribes an address that unsubscribed, bounced or marked an email as spam, or one that is on the suppression list. It sends no email, starts no automation and sends no webhook.

### Import a CSV file

The file needs a header row and one contact on each row. It can be up to 15 MB and 200,000 rows, in UTF-8 or Latin-1, with commas, semicolons or tabs between columns.

1. **Step 1 of 4: Choose a file.** Select **Choose a CSV file…** and pick the file.
2. **Step 2 of 4: Match the columns.** For each column, choose what it **Holds**: **Email address**, **First name**, **Last name**, **Tags** (separated by commas or semicolons), **Status**, **Custom field**, or **Do not import**. We suggest a match from the header and the first rows. Exactly one column must be the email address. Optionally choose **A column that marks a row unsubscribed**: a row with a yes, true or 1 in it is imported as unsubscribed and its address is suppressed. Select **Continue**.
3. **Step 3 of 4: Choose where they go.** Pick the **List** the contacts join, optionally enter **Tags for every contact**, and select **Start dry run**.
4. **Step 4 of 4: Check, then run.** Read the counts. Under **Run it for real**, choose **Add new contacts only** or **Add new and update existing**, tick **I hold these contacts' consent to email them**, and select **Run import**.

### Import from EmailOctopus

[Switch from EmailOctopus](/docs/organizations/commerce/email-marketing/#switch-from-emailoctopus) walks through this wizard, and what comes over.

### Read the counts

| Count | What it says |
| --- | --- |
| **New contacts** | Addresses the store did not know. |
| **Updated** | Known addresses whose names, fields or tags changed. Only **Add new and update existing** does this. |
| **Skipped** | Known addresses left alone. A dry run counts every known address here. |
| **Suppressed** | Addresses on the suppression list, which stay suppressed. |
| **Not valid** | Rows with an address that is not one. |
| **Repeated** | An address that appears again in the same file, imported once. |

An EmailOctopus import also counts **Lists**, **Tags** and **Fields**. **Import history** lists every import and dry run, newest first. Select one to open its own page, with its counts, the first 200 problems under **Problems** with the row and the reason, and **Cancel import…** while it runs. What a cancelled import already added stays. A file of up to 1,000 rows finishes at once. Larger imports run in the background and the page updates by itself.

A dry run keeps its file for 24 hours. After that, a run answers that it has expired, and you upload the file again. A real run needs your confirmation of consent, and it records `Imported from a file. The person importing it confirmed the contacts' consent.` as the contacts' consent.

## Bring in leads and customers

Leads and customers who agreed to marketing before email marketing existed are not contacts yet. Open **More contact actions**, then **Bring in leads and customers…**, to turn them into contacts on your default list:

- A lead that gave consent becomes a subscribed contact, with the words it agreed to.
- A customer who accepts marketing becomes a subscribed contact linked to their account.
- An address on the suppression list stays suppressed.

It tells you how many were added, updated and skipped. You can run it again: a contact that already has what it would be given is skipped. It allows 6 runs an hour. A lead or a customer who agrees after you run it joins by themselves.

## Result

- A contact appears in the list with its status, lists and tags as soon as it is added or imported.
- A **Subscribed** contact on a list receives that list's campaigns.
- A contact that unsubscribes, bounces or marks an email as spam leaves every list at once.

## Troubleshooting

**Add contact…** says the address is already a contact
: The address is in the store in this mode. Open the contact from the link, or use **Add new and update existing** when you import.

A contact I added never gets email
: It is **Pending** until it confirms, or **Unsubscribed**, **Bounced** or **Marked as spam**, or its address is on the suppression list. Open the contact and read its **Timeline** and **Consent**.

A bulk action says it changed fewer contacts than I picked
: **Add to a list…** skips contacts that are not subscribed. Other actions skip contacts the change does not apply to, such as removing a tag a contact does not have.

**Export CSV** says to narrow the export
: More than 100,000 contacts match. Filter by list, tag, status or date, and export in parts.

**Import** says the dry run expired
: A dry run keeps its file for 24 hours. Upload the file again. For EmailOctopus, send the API key again.

A segment cannot be deleted
: A scheduled, sending or paused campaign, or an automation that is on, uses it. The message names which. Change or cancel that first.

The **Import** view is missing
: Importing needs Tier 3 support or above.

## Related

- [Set up email marketing for your store](/docs/organizations/commerce/email-marketing/)
- [Collect signups with forms](/docs/organizations/commerce/email-forms/)
- [Write, test and send email campaigns](/docs/organizations/commerce/email-campaigns/)
- [Collect and export your store's leads](/docs/organizations/commerce/leads/)
- [Win back shoppers with marketing emails](/docs/organizations/commerce/marketing/#keep-an-address-off-the-flows)

## With the API

The routes are under `https://api.coritan.com/api/v1/orgs/{org_slug}/marketing/`. Reading takes `marketing.contacts:read` and writing takes `marketing.contacts:write` ([API key scopes](/docs/organizations/commerce/email-marketing/#api-key-scopes)). Another organization's contact is not found, and every write is recorded in the audit log without the contact's address.

### Add, find and change contacts

`GET /contacts` lists contacts and answers `{total, limit, offset, contacts}`. Its filters are `q`, `status`, `list_id`, `tag`, `segment_id`, `source`, `since` (included) and `until` (not included) on the day the contact was added, and `livemode`. `sort` is `created` (newest first), `email` or `engagement`, and `order` is `asc` or `desc`.

```bash
curl "https://api.coritan.com/api/v1/orgs/acme/marketing/contacts?status=subscribed&tag=vip&sort=engagement" \
  -H "X-API-Key: $ORG_API_KEY"
```

`POST /contacts` adds a contact and answers `201` with `{contact}`:

```bash
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/marketing/contacts" \
  -H "X-API-Key: $ORG_API_KEY" -H "Content-Type: application/json" \
  -d '{"email": "alex@example.com", "first_name": "Alex", "status": "subscribed",
       "consent_text": "Signed up at the shop counter.", "tags": ["counter"], "list_ids": [3]}'
```

| Field | What it takes |
| --- | --- |
| `email` | Required. |
| `first_name`, `last_name` | Up to 100 characters each. |
| `fields` | Custom field values by key. |
| `tags` | Tag names. A tag is created the first time it is used. |
| `list_ids` | Lists to join. The default list when left out. |
| `status` | `subscribed` (the default) or `pending`, which sends the confirmation email. |
| `consent_text` | Up to 500 characters. |
| `source` | Up to 64 characters. |
| `livemode` | `true` or `false`. The store's own mode when left out. |

A contact is `{id, email, first_name, last_name, status, fields, tags, list_ids, customer_id, source, livemode, consent, subscribed_at, unsubscribed_at, unsubscribe_reason, stats, created_at, updated_at}`. `status` is `subscribed`, `pending`, `unsubscribed`, `bounced` or `complained`. `consent` is `{text, at, method}`, and `stats` holds `emails_received`, `opens`, `clicks`, the last send, open and click times, and `engagement_score`. An address that is already a contact in this mode answers `409` with `contact_exists` and the contact's id.

- `GET /contacts/{contact_id}` answers `{contact, lists}`, where `lists` holds each list's id, name, the membership's status and when the contact joined.
- `PATCH /contacts/{contact_id}` changes `email`, the names, `fields` (merged, and `null` clears a key) and `tags` (the contact ends up with exactly the tags sent). `status` can only become `unsubscribed`. It can never go back to `subscribed` this way.
- `DELETE /contacts/{contact_id}` deletes the contact, and `?erase=true` also removes its events and its address from emails it got. It answers `{deleted: true, id}`.
- `GET /contacts/{contact_id}/timeline?limit&before` answers `{events}`, newest first. Send the last entry's `at` as `before` for the next page.
- `GET /contacts/{contact_id}/export` answers everything held about the contact as a JSON file. `GET /contacts/export` answers the CSV, with the same filters as the list. It allows 30 exports an hour, and refuses more than 100,000 rows with `422` and `too_many`.
- `POST /contacts/bulk` takes `ids` (up to 5,000) or a `filter` with the list's filters, and an `action`: `tag`, `untag` (with `tag`), `add_to_list`, `remove_from_list` (with `list_id`), `unsubscribe` or `delete` (`erase: true` also erases). It answers `{affected}`. An empty `filter` means every contact.
- `POST /contacts/backfill` brings in leads and customers and answers `{created, updated, skipped}`. Add `?livemode=` to limit it to one mode.

### Lists, fields and tags

- `GET /lists` answers `{total, limit, offset, lists}`, the default first, with each list's `counts` of `subscribed`, `pending` and `unsubscribed`. `?archived=true` lists the archived ones. `POST /lists` makes one. `GET`, `PATCH` and `DELETE /lists/{list_id}` read, change and delete one. A list has `name`, `description`, `double_opt_in`, `confirmation_subject`, `confirmation_body`, `welcome_enabled`, `welcome_subject`, `welcome_body`, `public`, `is_default` and `archived`. A name another list has answers `409` with `list_exists`.
- `DELETE /lists/{list_id}` answers `409` with `list_in_use` for a list with members. `?force=true` archives it. The default list answers `409` with `default_list`.
- `GET /lists/{list_id}/stats?days=30` answers `{counts, growth}`, with `{date, joined, left}` for each day, up to 365 days.
- `GET /lists/{list_id}/members?status&q` lists the members, each with its `membership`. `POST /lists/{list_id}/members` takes `contact_ids` or `emails`, up to 1,000 of each, and answers `{added, skipped, not_found}`. `DELETE /lists/{list_id}/members/{contact_id}` takes a contact off the list.
- `GET /fields` lists custom fields. `POST /fields` takes `key`, `label`, `type` (`text`, `number`, `date`, `boolean` or `choice`), `choices` and `default_value`. `PATCH` changes the label, choices, default value and position. `DELETE /fields/{field_id}` answers `{deleted, id, contacts_cleared}`.
- `GET /tags` lists tags with `contacts` counts, and `?q=` filters by name. `POST /tags` takes a `name` and a `color` such as `#c0392b`. `PATCH` renames or recolours. `DELETE /tags/{tag_id}` answers how many contacts carried it. A name another tag has, in any case, answers `409` with `tag_exists`.

### Segments in the API

A segment's `rules` are `{"match": "all", "rules": [rule or group]}`, where a rule is `{"field", "op", "value"}`:

```bash
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/marketing/segments/preview" \
  -H "X-API-Key: $ORG_API_KEY" -H "Content-Type: application/json" \
  -d '{"rules": {"match": "all", "rules": [
        {"field": "tag", "op": "has", "value": "vip"},
        {"field": "last_open_at", "op": "within_days", "value": 30}]}}'
```

The answer is `{count, sample}`. `GET /segments/fields` lists every field with its operators, each with a `key`, a `label` and whether it `needs_value`. Add `?campaigns=true` to list your sent campaigns as choices. `POST /segments` saves a segment from `name`, `description` and `rules`, and answers `201` with `{segment}`, which holds `count` and `counted_at`. A name another segment has answers `409` with `segment_exists`. A refused rule answers `422` and names it in `field`, such as `rules.rules[2].op`. `PATCH /segments/{segment_id}` changes it, and changing the rules counts it again. `POST /segments/{segment_id}/refresh` answers `{count}`. `GET /segments/{segment_id}/contacts` lists the matches. `DELETE` answers `409` with `segment_in_use` and `used_by` while a campaign or an automation uses it.

### Import with the API

Every import route takes `marketing.contacts:write`, reading an import included.

1. `POST /imports/csv/preview` takes the file as the multipart field `file` and answers `columns`, a `sample`, `total_rows`, the `delimiter` and `encoding` it read, and a `suggested_mapping` from each column to `email`, `first_name`, `last_name`, `tags`, `status`, `field:<key>` or `null`.
2. `POST /imports` starts the import and answers `202` with `{import}`. For a file, send the multipart fields `file` and `options`, a JSON text. Send `mode: "dry_run"` first.

   ```bash
   curl -X POST "https://api.coritan.com/api/v1/orgs/acme/marketing/imports" \
     -H "X-API-Key: $ORG_API_KEY" \
     -F "file=@contacts.csv" \
     -F 'options={"mapping": {"Email": "email", "First name": "first_name", "Tags": "tags"}, "list_id": 3, "tags": ["imported"], "mode": "dry_run"}'
   ```

3. `GET /imports/{import_id}` reads the progress. An import is `{id, kind, status, mode, list_id, options, total, processed, counts, errors, error, started_at, completed_at, created_at}`. `status` is `pending`, `running`, `completed`, `failed` or `canceled`. `counts` holds `created`, `updated`, `skipped`, `suppressed`, `invalid`, `duplicates`, `lists`, `tags` and `fields`, and `errors` holds the first 200 problems as `{row, email, error}`.
4. `POST /imports/{import_id}/run` turns a finished dry run into a real run. Send `{"mode": "create_only", "consent_confirmed": true}`, or `"mode": "upsert"` to update known contacts too. Without `consent_confirmed` the answer is `422` with `consent_required`. Another kind of import answers `409` with `not_a_dry_run`, and a dry run older than 24 hours answers `409` with `expired`.
5. `POST /imports/{import_id}/cancel` stops a pending or running import. A finished one answers `409` with `import_finished`.

`mode` is `dry_run` (the default), `create_only` or `upsert`. `GET /imports` lists the imports, newest first.

For EmailOctopus, `POST /imports/emailoctopus/lists` answers `{lists}` with each list's `id`, `name`, `subscribed`, `unsubscribed`, `pending`, `fields` and `tags`. Send the account's `api_key`, or leave it out to use the key of the store's EmailOctopus integration. A key EmailOctopus refuses answers `422` with `emailoctopus_key_refused`. Then start the import with JSON:

```bash
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/marketing/imports" \
  -H "X-API-Key: $ORG_API_KEY" -H "Content-Type: application/json" \
  -d '{"kind": "emailoctopus", "mode": "dry_run", "list_ids": ["5c4f3b2a-1d0e-4a9b-8c7d-6e5f4a3b2c1d"], "options": {"tags": ["imported"]}}'
```

The API key is sealed on the import and removed when it ends. An import starts at most 30 times an hour.

## API

- `GET /api/v1/orgs/{org_slug}/marketing/contacts`: The store's contacts, one page, with how many match in all (total) (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-get-api-v1-orgs-org-slug-marketing-contacts)
- `POST /api/v1/orgs/{org_slug}/marketing/contacts`: Add a contact you hold the consent of (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-post-api-v1-orgs-org-slug-marketing-contacts)
- `POST /api/v1/orgs/{org_slug}/marketing/contacts/backfill`: Backfill contacts (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-post-api-v1-orgs-org-slug-marketing-contacts-backfill)
- `POST /api/v1/orgs/{org_slug}/marketing/contacts/bulk`: Bulk contacts (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-post-api-v1-orgs-org-slug-marketing-contacts-bulk)
- `GET /api/v1/orgs/{org_slug}/marketing/contacts/export`: Export contacts (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-get-api-v1-orgs-org-slug-marketing-contacts-export)
- `GET /api/v1/orgs/{org_slug}/marketing/contacts/{contact_id}`: Get contact (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-get-api-v1-orgs-org-slug-marketing-contacts-contact-id)
- `PATCH /api/v1/orgs/{org_slug}/marketing/contacts/{contact_id}`: Update contact (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-patch-api-v1-orgs-org-slug-marketing-contacts-contact-id)
- `DELETE /api/v1/orgs/{org_slug}/marketing/contacts/{contact_id}`: Delete a contact, its list memberships and its tags (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-delete-api-v1-orgs-org-slug-marketing-contacts-contact-id)
- `GET /api/v1/orgs/{org_slug}/marketing/contacts/{contact_id}/export`: Export contact (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-get-api-v1-orgs-org-slug-marketing-contacts-contact-id-export)
- `GET /api/v1/orgs/{org_slug}/marketing/contacts/{contact_id}/timeline`: Contact timeline (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-get-api-v1-orgs-org-slug-marketing-contacts-contact-id-timeline)
- `GET /api/v1/orgs/{org_slug}/marketing/lists`: The store's lists, the default first, each with its member counts (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-get-api-v1-orgs-org-slug-marketing-lists)
- `POST /api/v1/orgs/{org_slug}/marketing/lists`: Create a list (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-post-api-v1-orgs-org-slug-marketing-lists)
- `GET /api/v1/orgs/{org_slug}/marketing/lists/{list_id}`: One list with its member counts (subscribed, pending and unsubscribed) (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-get-api-v1-orgs-org-slug-marketing-lists-list-id)
- `PATCH /api/v1/orgs/{org_slug}/marketing/lists/{list_id}`: Change a list's settings (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-patch-api-v1-orgs-org-slug-marketing-lists-list-id)
- `DELETE /api/v1/orgs/{org_slug}/marketing/lists/{list_id}`: Delete a list nobody was ever on (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-delete-api-v1-orgs-org-slug-marketing-lists-list-id)
- `GET /api/v1/orgs/{org_slug}/marketing/lists/{list_id}/members`: List members (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-get-api-v1-orgs-org-slug-marketing-lists-list-id-members)
- `POST /api/v1/orgs/{org_slug}/marketing/lists/{list_id}/members`: Put existing contacts on the list, by contactids or emails (up to 1,000 of each) (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-post-api-v1-orgs-org-slug-marketing-lists-list-id-members)
- `DELETE /api/v1/orgs/{org_slug}/marketing/lists/{list_id}/members/{contact_id}`: Take a contact off the list (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-delete-api-v1-orgs-org-slug-marketing-lists-list-id-members-contact-id)
- `GET /api/v1/orgs/{org_slug}/marketing/lists/{list_id}/stats`: List stats (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-get-api-v1-orgs-org-slug-marketing-lists-list-id-stats)
- `GET /api/v1/orgs/{org_slug}/marketing/fields`: List fields (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-get-api-v1-orgs-org-slug-marketing-fields)
- `POST /api/v1/orgs/{org_slug}/marketing/fields`: Create a custom field (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-post-api-v1-orgs-org-slug-marketing-fields)
- `PATCH /api/v1/orgs/{org_slug}/marketing/fields/{field_id}`: Change a field's label, choices, default value or position (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-patch-api-v1-orgs-org-slug-marketing-fields-field-id)
- `DELETE /api/v1/orgs/{org_slug}/marketing/fields/{field_id}`: Delete field (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-delete-api-v1-orgs-org-slug-marketing-fields-field-id)
- `GET /api/v1/orgs/{org_slug}/marketing/tags`: The store's tags by name, each with how many contacts carry it (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-get-api-v1-orgs-org-slug-marketing-tags)
- `POST /api/v1/orgs/{org_slug}/marketing/tags`: Create a tag (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-post-api-v1-orgs-org-slug-marketing-tags)
- `PATCH /api/v1/orgs/{org_slug}/marketing/tags/{tag_id}`: Rename a tag or change its colour (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-patch-api-v1-orgs-org-slug-marketing-tags-tag-id)
- `DELETE /api/v1/orgs/{org_slug}/marketing/tags/{tag_id}`: Delete tag (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-delete-api-v1-orgs-org-slug-marketing-tags-tag-id)
- `GET /api/v1/orgs/{org_slug}/marketing/segments`: List segments (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-get-api-v1-orgs-org-slug-marketing-segments)
- `POST /api/v1/orgs/{org_slug}/marketing/segments`: Save a segment: a name and the rules that pick its contacts (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-post-api-v1-orgs-org-slug-marketing-segments)
- `GET /api/v1/orgs/{org_slug}/marketing/segments/fields`: Segment fields (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-get-api-v1-orgs-org-slug-marketing-segments-fields)
- `POST /api/v1/orgs/{org_slug}/marketing/segments/preview`: Count the contacts some rules match, without saving anything (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-post-api-v1-orgs-org-slug-marketing-segments-preview)
- `GET /api/v1/orgs/{org_slug}/marketing/segments/{segment_id}`: One segment with its rules and the count it had when it was last counted (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-get-api-v1-orgs-org-slug-marketing-segments-segment-id)
- `PATCH /api/v1/orgs/{org_slug}/marketing/segments/{segment_id}`: Change a segment's name, description or rules (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-patch-api-v1-orgs-org-slug-marketing-segments-segment-id)
- `DELETE /api/v1/orgs/{org_slug}/marketing/segments/{segment_id}`: Delete a segment (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-delete-api-v1-orgs-org-slug-marketing-segments-segment-id)
- `GET /api/v1/orgs/{org_slug}/marketing/segments/{segment_id}/contacts`: Segment contacts (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-get-api-v1-orgs-org-slug-marketing-segments-segment-id-contacts)
- `POST /api/v1/orgs/{org_slug}/marketing/segments/{segment_id}/refresh`: Count the segment's contacts again now and keep the count (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-post-api-v1-orgs-org-slug-marketing-segments-segment-id-refresh)
- `GET /api/v1/orgs/{org_slug}/marketing/imports`: The store's imports, newest first, without their problem lists (read one import for those) (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-get-api-v1-orgs-org-slug-marketing-imports)
- `POST /api/v1/orgs/{org_slug}/marketing/imports`: Start an import and answer 202 with it (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-post-api-v1-orgs-org-slug-marketing-imports)
- `POST /api/v1/orgs/{org_slug}/marketing/imports/csv/preview`: Read a CSV without importing it (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-post-api-v1-orgs-org-slug-marketing-imports-csv-preview)
- `POST /api/v1/orgs/{org_slug}/marketing/imports/emailoctopus/lists`: Emailoctopus lists (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-post-api-v1-orgs-org-slug-marketing-imports-emailoctopus-lists)
- `GET /api/v1/orgs/{org_slug}/marketing/imports/{import_id}`: One import with its progress, its counts and the first 200 problems (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-get-api-v1-orgs-org-slug-marketing-imports-import-id)
- `POST /api/v1/orgs/{org_slug}/marketing/imports/{import_id}/cancel`: Stop an import that is pending or running (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-post-api-v1-orgs-org-slug-marketing-imports-import-id-cancel)
- `POST /api/v1/orgs/{org_slug}/marketing/imports/{import_id}/run`: Turn a finished dry run into a real run with the same options (https://www.coritan.com/docs/api/reference/organizations/marketing/marketing/#op-post-api-v1-orgs-org-slug-marketing-imports-import-id-run)
