Broadcasts
Send bulk emails to your contacts or segments. Broadcasts support A/B testing, test sends, scheduling, and cancellation.
/broadcastsCreate 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.
{
"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"
}/broadcastsList all broadcasts with pagination.
{
"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
}
}/broadcasts/:idRetrieve a single broadcast with delivery statistics and configuration.
{
"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"
}
}/broadcasts/:idUpdate 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.
{
"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"
}/broadcasts/:id/sendSend 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.
{
"success": true
}/broadcasts/:id/testSend a test copy of the broadcast to a specific email address before sending to all recipients.
{
"email": "test@yourdomain.com"
}/broadcasts/:id/cancelCancel a broadcast that is 'queued' (including one scheduled for later) or 'sending'. Emails already handed off cannot be recalled; a draft needs no cancelling.
{
"success": true
}/broadcasts/:id/variantsList A/B test variants for a broadcast. Only available when the broadcast was created with A/B testing.
{
"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
}
]
}/broadcasts/:id/variant-statsGet 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.
{
"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.
| Placeholder | Replaced 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}}.
<p>Hi {{first_name}}, here's what shipped this month.</p>
<footer>
<a href="{{unsubscribe_url}}">Unsubscribe</a> ·
<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.
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);| Parameter | Description |
|---|---|
variants | 2-5 variants, each with name (max 10 chars), subject, optional html/text, and weight (1-100) |
test_sample_size | Percentage of audience for the test phase (5-50%, default: 20%) |
test_duration_minutes | How long to wait before picking a winner (30-1440 minutes, default: 120) |