# Moving a list off Mailchimp, ActiveCampaign or Ontraport

Read this before importing from another platform. The aim is to bring the
consent records as well as the addresses, so that on day one everyone's
status is known and nobody who left is ever emailed again.

## Into Awesomate's Contacts & email

The owner imports in the hub at https://hub.awesomate.ai/contacts/import
(Bring your list), or you prepare the import for them (below). It takes a spreadsheet (a CSV with an email column),
Mailchimp (an API key, which is used once and not kept, or the subscribed,
unsubscribed and cleaned export files), an ActiveCampaign export (one file per
status, or one file with a Status column) or an Ontraport export (with the
Bulk Email Status column). The hub reads each person's status, puts the
unsubscribed and bounced in first, and sorts everyone by the record of how
they agreed: a sign-up record, needs the owner's word, or unknown. At review
the owner picks the lists, gives their word for the people with no record if
they can, may say that everyone with a given tag is a customer they work with
regularly (which puts them on a customers list), and commits. They can undo an import until the end of that day.

You prepare the import; the owner commits it, because committing is the
owner's statement about who agreed. Your part: read the export's header row,
suggest which list each group or tag belongs on (below), make the list with
the owner's wording, and stage the import with `awesomate_crm_lists
{action:'import'}`. It answers the counts and a `reviewUrl`, and when its
`ownerTask.given` is true the account owner also has a Tasks card linking to
it. Only an Owner presses Import there. If the call fails or times out, call
`imports` before trying again: it may already be staged, and the same file is
never staged twice. Unzip a Mailchimp ZIP and pass its CSV files; for an
ActiveCampaign export split one file per status, pass `fileStatuses`.
An import you prepare keeps no sign-up record (no opt-in time, IP or
confirmation time), so everyone who agreed waits for the owner's word at
review, even from Mailchimp or ActiveCampaign.

### Recipe: an Ontraport list

1. The owner exports from Ontraport: Contacts, select all, Export, every
   field. They save the file and give you its path. Never ask them to paste
   it, and never paste its rows into the conversation.
2. Read the header row only. Check the Bulk Email Status column is there (an
   export without it is refused), and note the tags and fields worth keeping.
3. Propose the list: its name, the promise wording and how often, and, if a
   tag marks regular customers, a customers list for them. Get the owner's OK
   on the words, then make each list with `awesomate_crm_lists
   {action:'create'}`.
4. Stage the import: `awesomate_crm_lists {action:'import', localPath,
   platform:'ontraport', label, list, customers?: {tag, list}, note?}`. Give
   the owner the `reviewUrl` and the counts, and, only when `ownerTask.given`
   is true, tell them the card is also in their Tasks.
5. After the owner says it's done, check with `awesomate_crm_lists
   {action:'import_status', id}` (committed, and the result counts) and
   `{action:'members', key}` for the list.
6. Delete any file you wrote yourself along the way (a cleaned copy, a split
   file). The owner's own export is theirs to keep or delete.

## Bring these, not just the emails

- Status (subscribed, pending, unsubscribed, bounced, complained).
- When and how they signed up: time, source or form, IP if recorded,
  confirmation time if double opt-in.
- Groups, interests and tags (they map to lists or groups, see below).
- Custom fields worth keeping (suburb, customer type, last job).
- The last 12 months of clicks if available, to send to the most engaged
  first while a new sending address warms up.

## Status maps

| Platform | Status in the source | Becomes |
|---|---|---|
| Mailchimp | subscribed, with OPTIN_TIME and its IP, or CONFIRM_TIME | Can be emailed on the matching list, when the owner imports in the hub. From Claude, needs the owner's word |
| Mailchimp | subscribed, no sign-up record | Needs the owner's word before anyone emails them |
| Mailchimp | pending (never confirmed) | Never confirmed: not emailed. See `asking-permission.md` |
| Mailchimp | unsubscribed | Suppressed, permanently (a complaint if the reason says abuse or spam) |
| Mailchimp | cleaned (hard bounced) | Suppressed, permanently |
| Mailchimp | transactional, archived, nonsubscribed | Not on any marketing list |
| ActiveCampaign | Active (1), with an opt-in time and IP or a confirm time | Can be emailed, when the owner imports in the hub. From Claude, needs the owner's word |
| ActiveCampaign | Active (1), no sign-up record | Needs the owner's word |
| ActiveCampaign | Unsubscribed (2), Bounced (3) | Suppressed |
| ActiveCampaign | Unconfirmed (0) or anything else | Never confirmed: not emailed |
| Ontraport | Opted-in, Single opt-in, Double opt-in | Always needs the owner's word: Ontraport keeps no opt-in time or IP |
| Ontraport | Hard Bounce | Suppressed |
| Ontraport | Transactional Only, Under review, anything else | Never confirmed: not emailed (Transactional Only mixes people who left with people who never joined) |

Where to find them: Mailchimp exports an audience as a ZIP with subscribed,
unsubscribed and cleaned files (Audience, All contacts, Export Audience); the
API also returns `timestamp_signup`, `timestamp_opt` and `ip_opt`.
ActiveCampaign's API URL and key are under Settings, Developer. Ontraport's
API key and App ID are under Administration, Integrations; the email status
field is `bulk_mail` (Bulk Email Status in an export). For Awesomate, only
Mailchimp is read by API key; ActiveCampaign and Ontraport come in as export
files.

Platform field names change; check the export headers rather than trusting
this table blindly, and say so if a column you expected isn't there.

## Groups and tags: list or group?

- A group or interest someone **chose** on a sign-up form ("Tips", "Specials")
  is a promise: map it to a list.
- A tag the business **applied** ("customer", "Carindale", "VIP") is a fact:
  map it to a field or a group, never to a list.
- When a source has one big list and no groups, everyone goes on the list
  closest to what the sign-up form promised. Find the form wording if you
  can.

## Order of work

1. Read the source and report counts per status before importing anything.
2. Agree the lists (see `list-planning.md`).
3. Import, with suppressions first, so a suppressed address can never be
   re-added as subscribed by a later row. (Awesomate's import does this
   itself.)
4. The owner confirms anything that needs their statement.
5. Decide where new sign-ups go. If the new system takes sign-ups, point the
   website form and any automations at it. Awesomate's Contacts & email
   doesn't host a sign-up form, so keep the form in a tool that records what
   each person agreed to, and import from it.
6. **Keep the old platform open for 30 days after its last send.**
   Unsubscribe links in old emails must keep working for that long. At the
   end, import once more to catch late unsubscribes, then close it.
7. Leave the old platform's DNS records alone until it is closed.

## Things that go wrong

- Re-importing an old export after unsubscribes have happened, which
  re-subscribes people. Suppressions always win.
- Treating "pending" as subscribed. They never confirmed.
- Importing a "leads" or "event attendees" list with no consent as if it
  were subscribers.
- Mapping every tag to a list, so people are "on" ten lists they never
  chose.
- Sending to the whole imported list on day one from a new address. Warm up
  (see `deliverability.md`).
