Skip to content
Sendoff
Esc
↑↓navigate↵open⌘Jpreview
On this page

Importing your list

Bring subscribers over from any provider's CSV export, keeping unsubscribes, bounces and complaints.

Every email provider can export its subscribers as a CSV. Sendoff reads that file, works out which column is which, and shows you exactly what it’ll do before it writes anything.

Export everything

Export your whole list, not just active subscribers: unsubscribes, bounces and spam complaints too. Importing them is what stops Sendoff from emailing people who already asked to leave, which is the one mistake an import mustn’t make.

Some providers put each status in its own file, or leave bounces out of the main export. Import each file; Sendoff merges them, and an unsubscribe or bounce in any file wins.

Import

Give the agent the file and ask:

Import my subscribers from ~/Downloads/subscribers.csv into Sendoff.

It runs a dry run first and shows you the report:

  • The columns it used for the email, the name and the status
  • How many rows will be active, unsubscribed, bounced and complained
  • What it skipped, and why
  • What would change: new subscribers, existing subscribers who’d be suppressed, and addresses already there

Check the report, then tell the agent to import for real.

Big files

For a file of more than about a thousand rows, the agent doesn’t copy the addresses into the conversation, which would be slow and could introduce typos. If it can run commands (Claude Code, Codex), it creates a temporary API key with the subscribers:import scope, uploads the file with curl, and revokes the key when it’s done. You can run the same command yourself:

curl --data-binary @subscribers.csv \
  -H "Authorization: Bearer $SENDOFF_IMPORT_KEY" \
  -H "Content-Type: text/csv" \
  "https://news.example.com/api/v1/subscribers/import?dryRun=true"

Drop dryRun=true to import. Each import takes up to 25,000 rows and 10 MB; split bigger files. See the REST API for the full response.

How columns are found

Sendoff matches columns by their header, ignoring case, spaces and punctuation, so Email Address, email_address and EMAIL are all the same.

Field Headers it recognizes
Email email, email address, e-mail, mail, or any other header containing “email”. A file with no header row works too, if it’s just a list of addresses.
Name name, full name, display name, or first name and last name (joined, or either one alone)
Status status, state, subscription status, subscriber type and similar
Flags unsubscribed, bounced, complained, email disabled, do not email, and timestamps like unsubscribed at or Mailchimp’s UNSUB_TIME and CLEAN_TIME. Any value other than false or empty marks the row.
Opt-in flags subscribed, opted in, accepts marketing, and Ghost’s subscribed_to_emails. False marks the row unsubscribed.

Status values it understands include active, subscribed and confirmed; unsubscribed, cancelled and removed; bounced, cleaned and invalid; complained and spam. When a row has more than one signal, the most serious wins: complained, then bounced, then unsubscribed, then active.

Commas, semicolons and tabs all work as separators, and quoted values can contain commas and line breaks.

What gets skipped

Sendoff would rather leave someone out than email them by mistake, so it skips and reports:

  • Unconfirmed rows, marked pending, unconfirmed or inactive. They never finished signing up.
  • Statuses it doesn’t recognize, listed with how many rows had each one. If a value means something specific, like “lapsed”, edit the column to a status Sendoff knows and import again.
  • Rows without an email, and invalid addresses.

If it can’t find an email column at all, it lists the headers it saw. Rename the right one to email and try again.

What imports never do

  • Send email. Imported active subscribers count as already confirmed and get no confirmation email. Only import people who signed up to hear from you.
  • Reactivate anyone. An import can unsubscribe or suppress an existing subscriber, but it never moves someone back to active.

A file of only unsubscribes or bounces

Some exports are just a list of suppressed addresses, with no status column. Tell the agent what the file is (“these are all bounces”) and it imports every row with that status. With curl, add &status=bounced (or unsubscribed, or complained).

Past issues

To keep the issues you sent with your old provider too, see Importing past issues.

Last updated on

Was this page helpful?