Newsletters

Public REST API for managing newsletters, subscribers, and issues. Full CRUD plus subscribe / DOI confirm / unsubscribe / send-issue endpoints. Pairs with the embed widget and hosted subscribe page.

Newsletters are the recurring-send counterpart to broadcasts. Each newsletter has its own subscriber list, branding, double-opt-in (DOI) flow, and history of issues.

The newsletter API is exposed at `/v1/newsletters/...` and is API-key authenticated, scoped to your workspace.

Public sign-up flows:

• **Hosted subscribe page** — `/n/:slug` (Next.js dashboard app) renders a public subscribe page with your branding. Newsletter signups land here when shared via QR code or social link.
• **Embed widget** — `/n/:slug/embed` is an iframe-friendly subscribe form you can drop into any site with a single `<iframe>` tag. Branded to your newsletter's accent color and logo.
• **Direct API** — `POST /v1/newsletters/:id/subscribers` accepts subscribe requests programmatically, perfect for headless integrations.

Double opt-in (DOI):

When DOI is enabled (the default), a new subscriber is created with `status: 'pending'` and a confirmation email is sent. The subscriber clicks the confirm link, which sets `status: 'active'`. Issues only fan out to active subscribers.

Auto-suppression cascade:

When a subscriber's email is added to `suppression_list` (hard bounce, repeated soft bounces, complaint, or manual), all matching newsletter_subscribers rows for that email in the same workspace are automatically flipped to `status: 'unsubscribed'` with the suppression reason recorded in `metadata.unsubscribe_reason`. Bounce / Complaint badges show on the subscriber row in the dashboard so you can tell dead addresses from voluntary opt-outs at a glance.

Issue throughput:

Sending an issue queues one email per active subscriber, drained at the shared **12 emails/sec** worker limit (which sits under AWS SES's **14/sec** account ceiling). A newsletter with 5,000 active subscribers takes ~7 minutes to fully ship. The dashboard shows live progress while the issue is sending.
GET/v1/newslettersAPI Key

List all newsletters in the current workspace.

GET/v1/newsletters/:idAPI Key

Get a single newsletter by ID, including branding, DOI settings, and subscriber count.

ParameterTypeInDescription
idrequiredstringpathNewsletter UUID
POST/v1/newslettersAPI Key

Create a new newsletter. Accepts name, slug, from_email, from_name, accent_color, logo_url, doi_enabled, and other branding fields.

ParameterTypeInDescription
namerequiredstringbodyDisplay name (e.g. "Weekly Digest")
slugrequiredstringbodyURL slug — used in /n/:slug public pages
from_emailrequiredstringbodySender email — domain must be verified for sending
from_namestringbodyDisplay name on outbound emails
descriptionstringbodyShort description shown on the subscribe page
doi_enabledbooleanbodyWhether to require double-opt-in confirmation. Default: true
accent_colorstringbodyHex color for branded UI (subscribe page, confirm email, embed widget)
logo_urlstringbodyURL of your newsletter logo
PATCH/v1/newsletters/:idAPI Key

Update a newsletter. Only provided fields are updated.

ParameterTypeInDescription
idrequiredstringpathNewsletter UUID
DELETE/v1/newsletters/:idAPI Key

Delete a newsletter. Cascades to subscribers and issues.

ParameterTypeInDescription
idrequiredstringpathNewsletter UUID
GET/v1/newsletters/:id/subscribersAPI Key

List subscribers for a newsletter. Supports status filter (active / pending / unsubscribed), search, and pagination.

ParameterTypeInDescription
idrequiredstringpathNewsletter UUID
statusstringqueryactive, pending, or unsubscribed
searchstringqueryMatch against email or name
pagenumberqueryPage number (default: 1)
GET/v1/newsletters/:id/subscribers/:subscriberIdAPI Key

Get a single subscriber.

POST/v1/newsletters/:id/subscribersAPI Key

Add a subscriber. With DOI enabled, creates a pending subscriber and sends the confirmation email. With DOI disabled, creates an active subscriber immediately. Idempotent — duplicate emails return the existing subscriber.

ParameterTypeInDescription
idrequiredstringpathNewsletter UUID
emailrequiredstringbodySubscriber email (lowercased)
namestringbodySubscriber display name
sourcestringbodyTag indicating where the signup came from (e.g. "embed", "footer", "import")
metadataobjectbodyArbitrary JSON metadata to attach to the subscriber
POST/v1/newsletters/:id/subscribers/:subscriberId/unsubscribeAPI Key

Unsubscribe a subscriber. Flips status to unsubscribed and records the timestamp. The public one-click unsubscribe link in newsletter footers also lands here.

POST/v1/newsletters/:id/subscribers/:subscriberId/resend-confirmationAPI Key

Resend the DOI confirmation email to a pending subscriber. Useful when the original was lost in spam.

DELETE/v1/newsletters/:id/subscribers/:subscriberIdAPI Key

Permanently delete a subscriber. Use POST /unsubscribe to keep the record for analytics.

GET/v1/newsletters/:id/issuesAPI Key

List issues for a newsletter, ordered by created_at desc.

ParameterTypeInDescription
idrequiredstringpathNewsletter UUID
GET/v1/newsletters/:id/issues/:issueIdAPI Key

Get a single issue with HTML body, subject, and stats.

POST/v1/newsletters/:id/issuesAPI Key

Create a draft issue.

ParameterTypeInDescription
idrequiredstringpathNewsletter UUID
subjectrequiredstringbodyEmail subject line
html_bodyrequiredstringbodyHTML body (will be wrapped in newsletter branding)
preheaderstringbodyShort preview text shown in inbox
scheduled_forstringbodyISO 8601 datetime to send at; if omitted, the issue is created as a draft you must explicitly send
PATCH/v1/newsletters/:id/issues/:issueIdAPI Key

Update a draft or scheduled issue. Sent issues are immutable.

POST/v1/newsletters/:id/issues/:issueId/sendAPI Key

Send an issue immediately (or schedule it if scheduled_for is set on the issue). Fans out to active subscribers; pending and unsubscribed subscribers are skipped. Suppression list is checked at send time.

POST/v1/newsletters/:id/issues/:issueId/send-testAPI Key

Send a test render of an issue to a single email address (typically your own) so you can preview the final email before broadcasting to all subscribers. Does not affect issue status or delivery counts.

ParameterTypeInDescription
idrequiredstringpathNewsletter UUID
issueIdrequiredstringpathIssue UUID
torequiredstringbodyRecipient email address for the test send

Request

bash
curl -X POST https://api.posthawk.dev/v1/newsletters/news-uuid/issues/issue-uuid/send-test \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_api_key" \
  -d '{ "to": "you@yourdomain.com" }'