Managing campaigns
Compose a marketing email against a sending domain, preview merge tags, then send it now or schedule it for later — with a per-contact unsubscribe and budget-capped async delivery.
A campaign is a single marketing email sent to every subscribed contact on one of your sending domains — the domain’s contact pool. You compose it once — from, subject, and an HTML and/or text body — and MailBlastr fans it out, personalizing each copy with merge tags and appending a per-contact unsubscribe.
Campaigns are owner-scoped and created as a draft. A draft can be edited freely; once you send or schedule it, the body is locked and delivery proceeds asynchronously.
The campaign object
A campaign is returned in this shape:
{
"object": "campaign",
"id": "8f5c2a1e-7b3d-4f9a-9c12-2e6d4a7b8c90",
"name": "June newsletter",
"audience_id": "78261eea-8f8b-4381-83c6-79fa7120f1cf",
"segment_id": null,
"topic_id": null,
"from": "Acme <news@yourdomain.com>",
"subject": "What's new in June",
"html": "<p>Hi {{FIRST_NAME}}, here's the latest…</p>",
"text": "Hi {{FIRST_NAME}}, here's the latest…",
"reply_to": null,
"preview_text": null,
"status": "draft",
"scheduled_at": null,
"sent_at": null,
"created_at": "2026-06-23T10:00:00.000Z",
"ab_test": { "enabled": false },
"recurrence": null,
"parent_campaign_id": null
}Compose against a domain
Create a draft with `POST /campaigns`. The domain picks the recipients — the campaign fans out to that domain’s contact pool — and the from address must be on a verified domain you own (it does not have to match domain). Provide html, text, or both.
import { Mailblastr } from 'mailblastr';
const mb = new Mailblastr('mb_xxxxxxxxx');
const { data, error } = await mb.campaigns.create({
"domain": "yourdomain.com",
"from": "Acme <news@yourdomain.com>",
"subject": "What's new in June",
"html": "<p>Hi {{FIRST_NAME}}, here's the latest…</p>",
"name": "June newsletter"
});
console.log({ data, error });Merge tags
Before each copy is sent, MailBlastr substitutes the following tags for that contact. Unknown or missing fields render as an empty string — substitution never fails.
| Tag | Replaced with |
|---|---|
{{FIRST_NAME}} | The contact's first name (empty if unset). |
{{LAST_NAME}} | The contact's last name (empty if unset). |
{{EMAIL}} | The contact's email address. |
{{{MAILBLASTR_UNSUBSCRIBE_URL}}} | This contact's unique one-click unsubscribe URL. Use the triple-brace form so the URL is inserted raw. |
{{ FIRST_NAME }} works the same as {{FIRST_NAME}}.Unsubscribe is automatic
Every campaign copy gets a per-contact unsubscribe footer and an RFC 8058 List-Unsubscribe header injected automatically, so recipients can always opt out (and inbox providers can offer one-click unsubscribe). Unsubscribing flips the contact to unsubscribed in that audience, and they are skipped on future campaigns.
You can also place the unsubscribe link inline by including {{{MAILBLASTR_UNSUBSCRIBE_URL}}} in your body — the automatic footer is added regardless.
Send or schedule
When the draft is ready, call `POST /campaigns/:id/send`. With no body the campaign sends as soon as possible (status → queued). Pass scheduled_at with a future ISO timestamp to schedule it (status → scheduled).
import { Mailblastr } from 'mailblastr';
const mb = new Mailblastr('mb_xxxxxxxxx');
const { data, error } = await mb.campaigns.send('CAMPAIGN_ID', { "scheduled_at": "2026-07-01T09:00:00Z" });
console.log({ data, error });from, subject, and a body, and that the from-domain is verified. A draft is the only state you can send — once queued, scheduled, or sent, the send endpoint returns a validation_error.Cancel or stop a campaign
Changed your mind before a scheduled send fires? Call `POST /campaigns/:id/cancel`. This cancels the pending delivery and returns the campaign to draft, so you can edit it and send or schedule it again. A reputation-paused campaign can also be returned to draft; already-sent recipients remain in its deduplication ledger.
The same endpoint stops a campaign that is already queued. Copies already handed to the delivery provider cannot be recalled, but all remaining work is cancelled — for a staggered campaign (daily_batch_size) that means every future batch-day. A stopped campaign becomes canceled rather than draft, because it is partly mailed and a draft would be re-sendable; already-sent recipients stay in its deduplication ledger.
import { Mailblastr } from 'mailblastr';
const mb = new Mailblastr('mb_xxxxxxxxx');
const { data, error } = await mb.campaigns.cancel('CAMPAIGN_ID');
console.log({ data, error });Async, budget-capped delivery
Delivery is handled by a background fan-out job — the send call returns immediately. Each recipient is sent as an individual email. The complete campaign must fit inside the remaining monthly plan allowance plus prepaid credits before it is accepted; a 10,000-recipient campaign with only 500 sends available is rejected before sending any recipient. A staggered campaign (one with daily_batch_size) is budgeted one batch-day at a time instead, so it is accepted as long as the next day’s batch fits and it waits for the rolling window to refill when it does not. Daily and reputation warm-up capacity are pacing limits instead: eligible recipients are sent in safe batches and the untouched remainder resumes at the next rolling-window reset. Suppressed and unsubscribed contacts are skipped.
Statuses
The API reports a campaign in one of the following states.
| Status | Meaning |
|---|---|
draft | Created but not sent. Editable; the only state from which you can send. |
scheduled | A future scheduled_at was set. Will start sending at that time. Canceling a scheduled campaign returns it to draft and prevents the send. |
recurring | The campaign has a recurrence and acts as a template: each occurrence is sent as its own child campaign while the template stays in this status. Canceling it stops the recurrence and returns the template to draft. |
paused | Reputation protection stopped the remaining fan-out. Contact support for review; an admin reinstatement resumes only recipients that have not already been sent. |
queued | The send is in flight — the fan-out job is delivering copies to the audience. (Internally this is the sending phase; the API surfaces it as queued.) |
canceled | A queued send was stopped before it finished. Copies already handed off were still delivered; no further recipients are mailed. Terminal — a canceled campaign cannot be resumed or re-sent (unlike a canceled scheduled campaign, which returns to draft). |
sent | All copies have been handed off for delivery. |
failed | The campaign could not be delivered. |
draft, scheduled and recurring campaigns can be deleted; any other status returns a validation_error. Deleting a scheduled or recurring campaign also cancels its pending job. A campaign that is already queued or sent cannot be removed. Individual copies already handed to the delivery provider cannot be recalled — but a queued campaign can be stopped, which cancels all remaining work (for a staggered campaign, every future batch-day) and moves it to canceled.