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: thepub_...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/jsonBody:
{
"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
- Confirm both env vars are set. Missing either: stop and report which one.
- Validate the email format before sending. Reject obvious garbage locally; do not burn API calls on it.
- Set
utm_sourceandutm_mediumon every call. Unattributed subscribers are unmeasurable subscribers. - Send the POST. Log status code and the returned subscription ID.
- Map the result: 201 subscribed, 409 already present, anything else is a failure to surface, not swallow.
- 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: trueplusreactivate_existing: trueon an existing list re-mails people who already got the welcome. Fix: setsend_welcome_email: falsefor 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.