Skip to main content

Overview

A Shipstar mailing list is the audience for your release-notes emails. You can fill one by hand in the dashboard, but for a real product the list should mirror your own users: someone signs up and they start getting release notes; they close their account and the emails stop; they change their address and the next issue reaches the new one. The public API gives you three idempotent calls for that, and the dashboard covers the one-off import and export: All calls take an API token scoped to the project. List ids come from GET /email/lists, or create one with POST /email/lists. Management calls are free; only content generation spends credits.

The four things to know

Key by external_id. Send your own user id as external_id on every upsert. Shipstar then matches on it first, so when a user changes their email address the same recipient is updated in place instead of a second one appearing. Without it, matching falls back to the email address, which still works for simple lists. Three statuses. A recipient is active (gets emails), removed (you took them off; an upsert puts them back), or unsubscribed (they clicked the link in an email). Unsubscribed is terminal: no API call ever changes those rows, they just come back in the response as skipped_unsubscribed. Your sync does not need to track opt-outs itself. Upsert never deletes. PUT only adds and updates. Departed users have to be removed explicitly — either one at a time from your account-deletion path, or by diffing in a reconcile job (below). Retries are safe. Every call is idempotent per entry. A re-sent batch reports the repeats as unchanged / already_removed. Lists hold at most 500 active recipients; an upsert that would cross that line is rejected as a whole with a 400 so you can split it.

Recipe 1 — Signup and account-deletion hooks

The simplest integration: call the API from the code paths where a user is created, updated or deleted.
Run these after your own transaction commits, ideally from a background job so a Shipstar hiccup never fails a signup. If the call fails, retrying later is safe.

Recipe 2 — Nightly reconcile from your database

Hooks can miss events (a bulk import into your own database, a migration). A scheduled job that pushes the current truth and removes what is no longer there catches everything:
Python
Recipients without an external_id (added by hand or by an email-only import) are left alone by step 2, so a human-curated list and a synced list can coexist. Write calls are limited to 30 per minute per IP, which is 30,000 recipients a minute in 1000-entry batches — more than a list can hold. Read the RateLimit-Remaining header if you run several syncs from one host.

Recipe 3 — Zapier, n8n or Make

Use the generic HTTP / Webhook action; there is no dedicated Shipstar app and none is needed.
  1. Trigger: new row in your CRM, new customer in Stripe, new signup in your auth provider — whatever marks someone as a user.
  2. Action: HTTP request
    • Method PUT
    • URL https://api.shipstar.ai/api/v1/email/lists/<list id>/recipients
    • Headers Authorization: Bearer <api token>, Content-Type: application/json
    • Body (JSON):
  3. For the churn side, a second Zap on your “customer deleted / subscription cancelled” trigger sends DELETE https://api.shipstar.ai/api/v1/email/lists/<list id>/recipients/{{customer_id}} with the same header.
Map the trigger’s stable id to external_id so a later email change updates the same recipient.

Recipe 4 — One-off CSV import or export

For an initial load from a spreadsheet, open the list in the dashboard (Destinations → Email) and use Import CSV. The importer looks for these headers, case-insensitively: A file with no header row and email addresses in the first column also works. You get a preview with the detected columns and counts before anything is sent, and the import runs through the same upsert as the API, so re-importing an updated file updates rather than duplicates. Export CSV on the same page (or GET .../recipients/export.csv with a token) downloads every recipient with their status — a backup you can re-import later, or feed into another tool.

Checking your work

  • GET /email/lists/{id} shows active_count, removed_count and unsubscribed_count; after a sync, active_count should match the number of users you pushed.
  • GET /email/lists/{id}/recipients?q=<email> finds one person and shows their status and the fields you wrote.
  • In the dashboard, the list shows the same counts, and the Sync via API button prints ready-to-paste curl commands with the list id filled in.