Overview
Author campaigns in the console, send to an audience, and track delivery through the REST API.
Campaigns are authored in the BuildBase console: pick a template, pick an audience, send or schedule. The API is for driving and observing that pipeline from your backend, not for composing mail in code.
Note
There is no bb.email.send(). The server SDK has no email namespace. To
send one transactional message to one user, use
bb.notification.send() — it renders the event's
email template and respects the user's preferences. Campaigns are for
audience-scale sends.
// Send a campaign that was prepared in the console
const response = await fetch(
`https://api.console.buildbase.app/api/emails/campaigns/${campaignId}/send`,
{
method: 'POST',
headers: { Authorization: process.env.BUILDBASE_API_TOKEN! },
}
);Before you start
Before a campaign can send, you need a verified sending domain, a sender address on that domain, and a template. The console blocks the send until all three exist.
The pipeline
- Template — the reusable body, with merge tags. See templates and merge tags.
- Sender and domain — who it comes from, on a verified domain.
- Campaign — a template plus an audience plus a schedule.
- Drafts — one queued message per recipient, materialized from the campaign.
- Send — the queue delivers, and history records the outcome per recipient.
Drafts are the step that surprises people. A campaign does not send directly to an audience: it first expands into per-recipient drafts, and the send walks those. That is what makes partial retry possible.
Campaign endpoints
All require an Authorization header — see the admin API
for the token format and list-query parameters.
| Method | Path | Purpose |
|---|---|---|
GET | /api/emails/campaigns | List campaigns, paginated |
GET | /api/emails/campaigns/:id | One campaign |
POST | /api/emails/campaigns | Create |
PATCH | /api/emails/campaigns/:id | Update |
POST | /api/emails/campaigns/:id/create-drafts | Expand the audience into per-recipient drafts |
POST | /api/emails/campaigns/:id/send | Send the campaign |
POST | /api/emails/campaigns/:id/retry-failed | Re-queue only the recipients that failed |
DELETE | /api/emails/campaigns/:id | Delete |
retry-failed re-queues failures without touching recipients who already
received the message, so it is safe to call more than once after a partial
delivery failure. It returns the counts it acted on:
{
"message": "Retried 12 failed emails, 3 skipped",
"retried": 12,
"skipped": 3
}Campaign status
A campaign is always in one of five states, and the action endpoints are guarded by it:
| Status | Meaning |
|---|---|
draft | Editable, not yet dispatched |
sending | Dispatch in progress |
sent | Dispatch finished |
failed | Dispatch failed |
archived | Retired |
| Endpoint | Allowed from | Rejected with |
|---|---|---|
/create-drafts | draft only | 400 — Cannot create drafts for campaign in "X" state |
/send | draft or failed | 400 — Cannot send campaign in "X" state |
/send accepting failed is what makes a whole-campaign retry possible after
a dispatch collapses. Use /retry-failed instead when the campaign partly
succeeded — re-sending from failed state is coarser.
Both rejections use the { error: true, message, path: "status" } shape.
Delivery history
Every send writes a history row per recipient with a status, and tracking records opens and clicks when the template includes the tracking pixel. Both are exposed as chart endpoints the console renders, and as ordinary list endpoints you can page through.
Delivery is asynchronous. /send returns { "message": "Campaign dispatch started" } as soon as the work is queued — that is not a delivery
confirmation. Poll the history rows for per-recipient outcomes.
Recipients are resolved from the campaign's audience list and expanded in batches of 500, so a large list produces drafts progressively rather than in one pass.
Unsubscribes
Two merge tags render opt-out links. Both resolve at send time to a page on your application URL — not the API — carrying a per-recipient token, so the link identifies the recipient without a login:
| Merge tag | Resolves to |
|---|---|
{{unsubscribe}} | <your-app>/unsubscribe-email-group?token=… |
{{manage-preference}} | <your-app>/manage-email-preference?token=… |
The preference center is backed by its own token-validated API under
/api/manage-email-preference, which is what the page calls to read and update
preferences. You do not call it directly.
Every audience campaign should carry at least one of these tags.
Providers
BuildBase sends through Google, Mailgun, and custom SMTP. Domain verification is Mailgun-backed, so a verified sending domain requires Mailgun regardless of which provider delivers a given sender.
Next Steps
- Templates and merge tags — compose the body.
- Sending domains — verify a domain before the first send.
- Notifications — one-off transactional mail.