Broadcasts
Broadcasts send one email campaign to many recipients: newsletters, product updates, and announcements. You create a draft, then send it immediately or schedule it. Pingram builds the audience, throttles delivery, and tracks per-recipient results.
You can do this from the Broadcast tab in the dashboard or with the Broadcasts API.
Create a draft
POST /broadcasts creates a draft. Nothing is sent until you call send or schedule.
const broadcast = await pingram.broadcasts.create({ name: 'August product update', type: 'product_updates', channel: 'email', audience: { filter: { $and: [ { email: { $exists: true } }, { 'properties.plan': { $eq: 'pro' } } ] } }, fromName: 'Acme', fromAddress: 'updates@acme.com', replyToAddress: 'hello@acme.com', subject: 'What we shipped, {{ user.properties.firstname }}', html: '<p>Hi {{ user.properties.firstname | default: "there" }},</p><p><a href="{{ pingram.unsubscribe }}">Unsubscribe</a></p>'});typeis the notification type key. Recipients can unsubscribe from it independently of other types. It is created automatically if it does not exist yet.channelis'email'.subjectandhtmlare Liquid templates rendered per recipient. Always include{{ pingram.unsubscribe }}.- Recipients who already unsubscribed from
typeare skipped automatically.
Audience
audience takes exactly one of:
filter — a Mongo-style query against your identified users. Best for segments and large audiences.
Allowed fields: email and properties.<key>. Allowed operators: $and, $or, $exists, $eq, $ne, $gt, $gte, $lt, $lte, $in. Nesting depth is at most 3, with at most 20 clauses.
Identify users first so property filters have data to match:
await pingram.user.identify('user-123', { email: 'dana@acme.io', properties: { plan: 'pro', firstname: 'Dana' }});emails — a raw list of addresses (max 10,000). Each address creates a user if one does not exist. For larger audiences, identify users and use filter.
Personalization
Subject and HTML are rendered with Liquid once per recipient. Custom fields stay under user.properties — use {{ user.properties.firstname }}, not {{ user.firstname }}.
| Tag | Source |
|---|---|
{{ user.id }} |
Your user id. For audience.emails, this is the lowercased email. |
{{ user.email }} |
Email address |
{{ user.properties.<key> }} |
Custom properties from user.identify |
{{ pingram.unsubscribe }} |
Per-recipient unsubscribe URL for this broadcast’s type |
Missing properties render as empty. Use | default: when you need a fallback. audience.emails only guarantees user.id and user.email unless those users were identified first.
Send or schedule
await pingram.broadcasts.send(broadcast.broadcastId);
await pingram.broadcasts.schedule(broadcast.broadcastId, { sendAt: '2026-10-01T15:00:00.000Z'});Send requires a non-empty subject and a type. Cancel a scheduled broadcast to return it to draft.
Verify your sending domain for full-speed delivery. Broadcasts from unverified domains, including the shared default sender, are heavily throttled. See Domain Verification.
Metrics
const metrics = await pingram.broadcasts.getMetrics(broadcast.broadcastId);// status, total, sent, delivered, opened, clicked,// bounced, complained, unsubscribed, skipped, failedStatus moves draft → sending → sent (or scheduled, paused, canceled). If a broadcast pauses itself, pausedReason explains why.