Segments and campaigns
A campaign is one email to many people, right now or on a schedule. A segment is a saved filter you can point campaigns at.
import { mailroom } from "./client";
// Segments are evaluated when the campaign runs, not when it is created.
const segment = await mailroom.segments.create({
label: "Engaged beta users",
filter: { status: "subscribed", tags: ["beta"] },
});
const campaign = await mailroom.campaigns.create({
name: "Beta feature launch",
template: "product-update",
subject: "New in beta this week",
audience: { mode: "segment", segmentId: segment.id },
scheduleAt: "2026-08-01T15:00:00Z", // omit to send immediately
variants: [
{ key: "a", subject: "New in beta this week", weight: 1 },
{ key: "b", subject: "You asked, we shipped", weight: 1 },
],
});
console.log(campaign.audienceSize, campaign.status);
// Progress: counters update as sends and engagement events land.
const record = await mailroom.campaigns.get(campaign.campaignId);
console.log(record.sent_count, record.opened_count, record.clicked_count);
// Stop a scheduled or in-flight campaign. Already-sent emails are unaffected.
await mailroom.campaigns.cancel(campaign.campaignId);
Audiences
audience | Targets |
|---|---|
{ mode: "entire" } | Every subscribed contact in the project |
{ mode: "segment", segmentId } | Contacts matching a saved segment |
{ mode: "tag", tag } | Contacts carrying that tag |
{ mode: "people", emails: [...] } | An explicit list (at least one address) |
Unsubscribed and suppressed contacts are excluded no matter which mode you pick.
Segments
A segment is a label plus a filter over status, tags, and source:
const segment = await mailroom.segments.create({
label: "Engaged beta users",
filter: { status: "subscribed", tags: ["beta"] },
});
Filters are evaluated when the campaign runs, not when the segment is created —
so a segment always reflects current membership. segments.list() returns each
segment with a count; labels are unique per project (a duplicate is rejected 422).
Segment writes use the contacts:write scope, not a segment-specific one.
Scheduling
Omit scheduleAt to start immediately. Pass an ISO instant to queue it; add
endsAt to stop a long-running send at a deadline instead of letting it finish.
await mailroom.campaigns.create({
template: "sale",
audience: { mode: "entire" },
scheduleAt: "2026-08-01T15:00:00Z",
endsAt: "2026-08-02T15:00:00Z",
});
Campaign sends are queued and drained by a dispatcher, so create() returns as
soon as the audience is resolved — audienceSize and enrolled tell you how many
recipients it found and enqueued.
A/B variants
Two to four arms, split by relative weight (not percentages):
variants: [
{ key: "a", subject: "New in beta this week", weight: 1 },
{ key: "b", subject: "You asked, we shipped", weight: 1 },
]
Each arm can override the subject. Per-arm counters land on the campaign record alongside the totals.
Progress and cancellation
const record = await mailroom.campaigns.get(campaignId);
// status: draft | scheduled | queued | sending | sent | cancelled | failed
// counters: total_recipients, sent_count, delivered_count, opened_count,
// clicked_count, bounced_count
const { cancelledJobs } = await mailroom.campaigns.cancel(campaignId);
Cancelling drops queued jobs; emails already handed to the provider are gone and cannot be recalled.
Scopes
campaigns:write to create and cancel; contacts:write for segment writes. Reads
need only a valid key.