Campaigns

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.

TagReplaced 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.
Whitespace inside the braces is tolerated — {{ 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 });
Sending checks the campaign has a 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.

StatusMeaning
draftCreated but not sent. Editable; the only state from which you can send.
scheduledA future scheduled_at was set. Will start sending at that time. Canceling a scheduled campaign returns it to draft and prevents the send.
queuedThe 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.)
sentAll copies have been handed off for delivery.
failedThe campaign could not be delivered.
Only 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.