Skip to content

CSV contact import and export #38

Closed

Description

@clbotdev

There's no way to bulk-import or export contacts, yet apps/docs/content/docs/getting-started/first-broadcast.mdx:8 says "Create or import contacts". Without import, nobody can move off Mailchimp, Beehiiv, Substack, ConvertKit and the like.

Import

  • CSV upload with column mapping. Recognize column names from common platform exports automatically: email, first/last name, tags, status, signup date, and custom fields.
  • Parse in the browser and send chunks (about 1000 rows) to a bulk endpoint, validating again on the server. Or upload the file and process it in a BullMQ job with progress.
  • Statuses can only get stricter: active < unsubscribed < bounced < complained. An import never moves a suppressed or unsubscribed contact back to active, and bounced/complained rows create suppressions. (This ranking comes from https://github.com/marclou/mailcheap lib/csv-import.js.)
  • Optionally import historic open and click rates (as % or 0–1) to seed engagement-ordered sending (Warmup scheduler #5) and dormancy (Dormant contacts segment and re-engagement sequence #29).
  • Keep imported signup dates, so growth charts don't show an import-day spike.
  • Return a report of created, updated and skipped rows, with the reason for each skip.

Export

  • Export all contacts, or a segment, to CSV, including tags, custom fields, subscription status and suppression reason.

Add the REST endpoints, MCP tools and docs.

Activity

  1. moved this from Backlog to Ready in SendLit Roadmapon Oct 10, 2026
  2. clbotdev commented on Oct 19, 2026

    @clbotdev
    ContributorAuthor

    Marking this severity: critical. It blocks inviting beehiiv switchers: without import they can't move their list. Specifics from the 19 Oct readiness audit:

    beehiiv export format (beehiiv help articles 12258595483543 and 12428610562327)

    • Export from Settings → Export Data. Download links expire after 24h.
    • Two variants:
      • Quick/Basic: email, status, tier
      • Full: adds custom fields and stats (created/subscribed date, UTM/acquisition, referral counts). Headers vary by publication, so we need a column-mapping UI, not fixed headers.
    • Statuses: Active / Paused / Pending / Needs Approval / Inactive. Some exports or tools also show Unsubscribed or Bounced.

    Proposed status mapping

    beehiiv SendLit
    Active subscribed
    Paused subscribed, tag beehiiv-paused (or let the user choose to unsubscribe)
    Inactive / Unsubscribed imported as unsubscribed (never re-subscribe on import)
    Bounced unsubscribed and added to suppressions
    Pending / Needs Approval skipped by default, or imported unsubscribed with tag beehiiv-pending
    tier (free/premium) tag tier:<value>
    custom fields custom fields (mapped in the UI)

    Gaps in the current API

    • The create schema doesn't accept subscribed (packages/api-contract/src/schemas/contacts.ts ~L26-28: email, name and tags only; subscribed is on update only). Importing an unsubscribed contact would need create + PATCH, with a window where they look subscribed.
    • No bulk endpoint: only single POST /contacts (packages/api-contract/src/contract.ts:133) and MCP create_contact. We need a bulk upsert API (and an MCP tool), with idempotent upsert by email, per-row errors and an async job for large files.
    • Admin alerting on large imports for hosted abuse prevention is tracked in Alert admins on large contact imports for review (hosted) #74 (part of Hosted abuse prevention for open signups #75).
    • Docs: apps/docs/content/docs/getting-started/first-broadcast.mdx:8 already says "Create or import contacts". The migration guides are tracked in Migration guides: beehiiv, Substack and Mailchimp to SendLit #68.
  3. moved this from Ready to In progress in SendLit Roadmapon Oct 19, 2026
  4. moved this from In progress to Done in SendLit Roadmapon Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestseverity: criticalBreaks sending, data loss or security; fix now

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions