Skip to content

Broadcasts

Send bulk emails to your contacts or segments. Broadcasts support A/B testing, test sends, scheduling, and cancellation.

POST
/broadcasts

Create a draft broadcast. segment_id is the segment's seg_… id (a legacy numeric id string is still accepted). Optionally pass topic_id (a top_… subscription topic): contacts who opted out of that topic are skipped at send time. The response carries segmentPublicId and topicPublicId. A broadcast never sends on create: call POST /broadcasts/:id/send, which also honours scheduled_at.

json
{
  "name": "March Newsletter",
  "from": "Newsletter <newsletter@yourdomain.com>",
  "subject": "What's new in March",
  "html": "<h1>March Updates</h1><p>Here's what happened...</p>",
  "text": "March Updates: Here's what happened...",
  "reply_to": "hello@yourdomain.com",
  "segment_id": "seg_abc123def456",
  "topic_id": "top_newsletter123",
  "scheduled_at": "2026-03-25T09:00:00.000Z"
}
GET
/broadcasts

List all broadcasts with pagination.

json
{
  "data": [
    {
      "id": 214,
      "publicId": "bc_abc123def456ghi789",
      "name": "March Newsletter",
      "status": "sent",
      "totalRecipients": 1250,
      "deliveredCount": 1230,
      "openedCount": 456,
      "clickedCount": 89,
      "bouncedCount": 12,
      "sentAt": "2026-03-25T09:00:00.000Z",
      "createdAt": "2026-03-23T10:00:00.000Z"
    }
  ],
  "meta": {
    "page": 1,
    "perPage": 20,
    "total": 24,
    "totalPages": 2
  }
}
GET
/broadcasts/:id

Retrieve a single broadcast with delivery statistics and configuration.

json
{
  "broadcast": {
    "id": 214,
    "publicId": "bc_abc123def456ghi789",
    "name": "March Newsletter",
    "status": "sent",
    "fromAddress": "newsletter@yourdomain.com",
    "fromName": "Newsletter",
    "subject": "What's new in March",
    "htmlBody": "<h1>March Updates</h1><p>Here's what happened...</p>",
    "segmentId": 1,
    "segmentPublicId": "seg_abc123def456",
    "segment": { "name": "Newsletter", "publicId": "seg_abc123def456" },
    "topicPublicId": "top_newsletter123",
    "topic": { "name": "Product updates", "publicId": "top_newsletter123" },
    "totalRecipients": 1250,
    "deliveredCount": 1230,
    "bouncedCount": 12,
    "openedCount": 456,
    "clickedCount": 89,
    "sentAt": "2026-03-25T09:00:00.000Z",
    "createdAt": "2026-03-23T10:00:00.000Z"
  }
}
PATCH
/broadcasts/:id

Update a draft broadcast. All fields are optional. Only works for broadcasts in 'draft' status. segment_id takes a seg_… id; topic_id takes a top_… id, or null to detach the topic.

json
{
  "subject": "Updated: What's new in March",
  "topic_id": "top_newsletter123",
  "html": "<h1>Updated March Newsletter</h1><p>New content here.</p>",
  "scheduled_at": "2026-03-26T09:00:00.000Z"
}
POST
/broadcasts/:id/send

Send a draft broadcast. The broadcast must be in 'draft' status and its from domain verified. It moves to 'queued' and goes out immediately — or, if scheduled_at is in the future, at that time.

json
{
  "success": true
}
POST
/broadcasts/:id/test

Send a test copy of the broadcast to a specific email address before sending to all recipients.

json
{
  "email": "test@yourdomain.com"
}
POST
/broadcasts/:id/cancel

Cancel a broadcast that is 'queued' (including one scheduled for later) or 'sending'. Emails already handed off cannot be recalled; a draft needs no cancelling.

json
{
  "success": true
}
GET
/broadcasts/:id/variants

List A/B test variants for a broadcast. Only available when the broadcast was created with A/B testing.

json
{
  "variants": [
    {
      "id": 1,
      "broadcastId": 214,
      "name": "A",
      "subject": "What's new in March",
      "weight": 50,
      "recipientCount": 125,
      "deliveredCount": 124,
      "openedCount": 46,
      "clickedCount": 9,
      "bouncedCount": 1
    },
    {
      "id": 2,
      "broadcastId": 214,
      "name": "B",
      "subject": "March product updates",
      "weight": 50,
      "recipientCount": 125,
      "deliveredCount": 125,
      "openedCount": 56,
      "clickedCount": 11,
      "bouncedCount": 0
    }
  ]
}
GET
/broadcasts/:id/variant-stats

Get per-variant performance for an A/B test (rates are fractions of recipientCount) and the winning variant id once one has been picked (null before). Returns 422 for a broadcast without A/B testing.

json
{
  "variants": [
    {
      "id": 1,
      "name": "A",
      "subject": "What's new in March",
      "weight": 50,
      "recipientCount": 125,
      "deliveredCount": 124,
      "openedCount": 46,
      "clickedCount": 9,
      "bouncedCount": 1,
      "openRate": 0.368,
      "clickRate": 0.072
    },
    {
      "id": 2,
      "name": "B",
      "subject": "March product updates",
      "weight": 50,
      "recipientCount": 125,
      "deliveredCount": 125,
      "openedCount": 56,
      "clickedCount": 11,
      "bouncedCount": 0,
      "openRate": 0.448,
      "clickRate": 0.088
    }
  ],
  "winnerVariantId": 2
}

Personalization

Both the HTML and plain-text bodies are personalized per recipient at send time. Placeholders are case-insensitive, and anything PostStack does not recognise is left exactly as written — so a misspelt tag is delivered literally rather than silently blanked. Send a test to yourself before the real thing.

PlaceholderReplaced with
{{first_name}}The contact's first name. Renders as an empty string when it is not set, so prefer a greeting that still reads correctly when it is missing.
{{last_name}}The contact's last name. Also empty when unset.
{{email}}The contact's email address.
{{unsubscribe_url}}A signed, per-recipient unsubscribe link. Opening it asks the recipient to confirm — so a scanner or prefetcher following links on their behalf cannot opt them out — and confirming suppresses the address and marks the contact unsubscribed.
{{preferences_url}}A signed link to the contact's preference centre, where they can turn individual subscription topics on or off instead of opting out entirely.

Broadcasts always carry RFC 8058 one-click unsubscribe headers automatically. A visible unsubscribe link is separate from those, and Gmail and Outlook both expect one once you send at bulk volumes — add it with {{unsubscribe_url}}.

html
<p>Hi {{first_name}}, here's what shipped this month.</p>

<footer>
  <a href="{{unsubscribe_url}}">Unsubscribe</a> &middot;
  <a href="{{preferences_url}}">Manage preferences</a>
</footer>

A/B Testing

Test different subject lines and content by creating broadcasts with A/B test variants. PostStack sends each variant to a sample of your audience, measures performance, then automatically sends the winning variant to the rest.

typescript
const broadcast = await poststack.broadcasts.create({
  name: 'A/B Test: Subject Lines',
  from: 'Newsletter <newsletter@yourdomain.com>',
  subject: 'Default subject (used as fallback)',
  html: '<p>Newsletter content</p>',
  segment_id: 'seg_abc123def456',
  ab_test: {
    variants: [
      {
        name: 'A',
        subject: "What's new in March",
        weight: 50,
      },
      {
        name: 'B',
        subject: 'March product updates',
        weight: 50,
      },
    ],
    test_sample_size: 20,      // Send to 20% of audience first
    test_duration_minutes: 120, // Wait 2 hours before picking winner
  },
});

await poststack.broadcasts.send(broadcast.publicId);
ParameterDescription
variants2-5 variants, each with name (max 10 chars), subject, optional html/text, and weight (1-100)
test_sample_sizePercentage of audience for the test phase (5-50%, default: 20%)
test_duration_minutesHow long to wait before picking a winner (30-1440 minutes, default: 120)

Related

Broadcasts overviewNewsletters and one-off campaigns to segments, with unsubscribe handling.