Skip to main content
AC
Content & Publishing4.4 KBMIT licensed

beehiiv

Original, written for TechTide client work

Subscribe contacts to a Beehiiv newsletter publication via the Beehiiv v2 API, from forms, scripts, or automations. Use when pushing email addresses into Beehiiv, adding subscribers programmatically, or wiring a signup form to a publication. Not for publishing to Ghost, Substack, or Listmonk: use ghost-publisher, substack-publisher, or listmonk-publisher. Not for writing the newsletter content: use copywriting or emails.

  • beehiiv

SKILL.md

Beehiiv Subscriber Push

One job: put an email address into a Beehiiv publication, with UTM attribution, and confirm it landed. Treat every subscribe call as an external send: real inboxes get welcome emails.

Configuration

Two secrets, both from environment, never hardcoded:

  • BEEHIIV_API_KEY: from the Beehiiv dashboard, Settings > API.
  • BEEHIIV_PUBLICATION_ID: the pub_... identifier for the target publication.

API base: https://api.beehiiv.com/v2.

Subscribe call

POST https://api.beehiiv.com/v2/publications/$BEEHIIV_PUBLICATION_ID/subscriptions
Authorization: Bearer $BEEHIIV_API_KEY
Content-Type: application/json

Body:

{
  "email": "subscriber@example.com",
  "utm_source": "<SOURCE>",
  "utm_medium": "<MEDIUM>",
  "send_welcome_email": true,
  "reactivate_existing": true
}

Responses: 201 with a subscription object on success. 409 means already subscribed, treat as success for idempotent flows. 401 means bad or missing API key. 404 means wrong publication ID.

Shell one-liner:

curl -s -w '\n%{http_code}\n' \
  -X POST "https://api.beehiiv.com/v2/publications/$BEEHIIV_PUBLICATION_ID/subscriptions" \
  -H "Authorization: Bearer $BEEHIIV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"subscriber@example.com","utm_source":"<SOURCE>","utm_medium":"<MEDIUM>","send_welcome_email":true,"reactivate_existing":true}'

In Node or Bun, use native fetch with the same endpoint and read both secrets from process.env. No packages needed.

Workflow

  1. Confirm both env vars are set. Missing either: stop and report which one.
  2. Validate the email format before sending. Reject obvious garbage locally; do not burn API calls on it.
  3. Set utm_source and utm_medium on every call. Unattributed subscribers are unmeasurable subscribers.
  4. Send the POST. Log status code and the returned subscription ID.
  5. Map the result: 201 subscribed, 409 already present, anything else is a failure to surface, not swallow.
  6. Batch inputs: dedupe first, then send sequentially with a short delay. Respect rate limits; back off on 429.

Good vs bad

Bad: Loop over a CSV firing subscribes with no dedupe, no status handling, and send_welcome_email: true on a re-import. Existing subscribers get re-welcomed, failures vanish, attribution is blank.

Good: Dedupe the list, subscribe with source and medium set, treat 409 as already-in, collect failures into a retry list, and report counts: N new, M existing, K failed with reasons.

Verification

Send one test subscription with an address you control. Expect HTTP 201 and a JSON body containing an id starting with sub_, then the address visible in the Beehiiv dashboard audience list within a minute. If you get 401, re-check the API key. If 404, the publication ID is wrong. If 201 but the dashboard shows nothing, confirm you are looking at the same publication the ID points to.

Completion checklist

  • [ ] Secrets read from environment, not hardcoded anywhere
  • [ ] UTM source and medium set on every subscription
  • [ ] 201, 409, and error statuses each handled explicitly
  • [ ] Test subscription verified in the dashboard
  • [ ] Batch runs report new / existing / failed counts

Any box unchecked: not done. Fix or say so.

Footguns

  • Welcome email blasts on re-import. send_welcome_email: true plus reactivate_existing: true on an existing list re-mails people who already got the welcome. Fix: set send_welcome_email: false for imports and migrations; keep it true only for genuine new signups.
  • Treating 409 as an error. Retrying a 409 forever, or reporting it as a failure, corrupts run reports. Fix: 409 is a terminal, successful-enough state. Count it as "existing".
  • Committing the publication ID or key. Both identify a real account. Fix: env vars only, and keep them out of logs and error messages.

More in Content & Publishing

All skills