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": "aud_3b1c...",
"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 → sending). 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 sending, scheduled, or sent, the send endpoint returns a validation_error.Cancel a scheduled 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. Only a scheduled campaign can be canceled.
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 and counts against your account daily sending quota; if a campaign would exceed the remaining budget, the rest is queued and trickled out as quota frees, so no contact is silently dropped. 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. |
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.) |
sent | All copies have been handed off for delivery. |
failed | The campaign could not be delivered. |
draft and scheduled campaigns can be deleted. Deleting a scheduled campaign also cancels its pending send job. A campaign that is already queued or sent cannot be removed, and a send in flight cannot be recalled.