Campaigns
A campaign sends an email to all active contacts in a list. The process is:
create the campaign (in draft status) → optional: test it → send or schedule
→ analytics update in real time as the send progresses.
Operations
Section titled “Operations”Management
Section titled “Management”List campaigns
GET /v1/bulk/campaignsOptional parameters: status, limit (default 100), offset, key_id.
Create campaign
POST /v1/bulk/campaignsRequired body:
| Field | Type | Description |
|---|---|---|
name | string | Internal name of the campaign. |
template_id | string | ID of the template to send. |
list_id | string | ID of the target contact list. |
from_email | string | Sender address. Must be a verified domain. |
Optional body: from_name, reply_to.
Response: 201 with the campaign object in status: "draft".
Get campaign
GET /v1/bulk/campaigns/{id}Update campaign
PATCH /v1/bulk/campaigns/{id}Only available when status is draft or scheduled. Updatable fields:
name, template_id, list_id, from_email, from_name, reply_to, scheduled_at.
Sending and control
Section titled “Sending and control”Send or schedule
POST /v1/bulk/campaigns/{id}/sendOptional body: { "scheduled_at": "2026-07-01T09:00:00Z" }. Without a body (or
without scheduled_at) the send is immediate. Response: 200.
Send test email
POST /v1/bulk/campaigns/{id}/testRequired body: { "to": "yo@miempresa.com" }. Optional body: { "sample_data": {} }.
Renders the template with sample data and sends it to the specified address. Response: 200.
Pause
POST /v1/bulk/campaigns/{id}/pausePauses a send in progress. Response: 200. Returns 409 if the campaign
is not in sending.
Resume
POST /v1/bulk/campaigns/{id}/resumeResumes a paused campaign. Response: 200. Returns 409 if the campaign
is not in paused.
Cancel
POST /v1/bulk/campaigns/{id}/cancelCancels the campaign. Available from draft, scheduled, sending, or paused.
Returns 409 if already in sent or cancelled.
Duplicate
POST /v1/bulk/campaigns/{id}/duplicateCreates a new draft with the same parameters. Response: 201 with the
new campaign object.
Analytics
Section titled “Analytics”Detailed analytics
GET /v1/bulk/campaigns/{id}/analyticsReturns engagement metrics: opens (unique, total, rate), clicks (unique,
total, rate, ctor), bounces, complaints, unsubs, rates (open/ctr/ctor/bounce/complaint/unsub),
top_links[] and timeline[]. Optional parameter: format=csv to export.
Delivery report
GET /v1/bulk/campaigns/{id}/reportDelivery statistics for the campaign.
Deliverability
GET /v1/bulk/campaigns/{id}/deliverabilityCrosses sends + bounces + complaints. Includes delivery_rate, bounce_rate,
complaint_rate and a breakdown by destination domain.
Cross-campaign performance
GET /v1/bulk/campaigns/performanceOptional parameter: key_id. Returns an array of objects with id, name,
status, started_at, delivered, opens_unique, clicks_unique, bounced,
open_rate, click_rate, and bounce_rate.
Aggregated rates
GET /v1/bulk/analytics/ratesOptional parameters: period (24h | 7d | 30d, default 7d), key_id,
format=csv. Returns period, granularity, window_start, totals,
rates, and series[].
Re-confirmation campaigns
Section titled “Re-confirmation campaigns”An old list that never gets pruned is the number one cause of hard bounces, and continuing to write to people who never open is what makes your email land in spam for the people who do read you.
The full cycle is three steps:
1. Segment the inactive. With an engagement rule:
{ "field": "engagement", "op": "not_opened", "days": 180 }It only counts human opens: Apple Mail Privacy Protection’s automatic prefetches don’t count as opening, so this segment is people who genuinely didn’t see your email.
2. Send them a campaign with {{reconfirm_url}}. A single CTA: “yes, I still
want these”. Whoever clicks it sees a confirmation page and is recorded as
active — they leave the inactive segment and the pruning.
3. Unsubscribe whoever didn’t respond.
GET /v1/bulk/campaigns/{id}/sunsetPOST /v1/bulk/campaigns/{id}/sunsetThe GET tells you how many it would affect before doing anything. The POST
marks as unsubscribed those who didn’t open, didn’t click and didn’t
re-confirm. Response: { "unsubscribed": 4312 }.
Their transactional email is unaffected: unsubscribing from marketing can’t leave someone without their password reset.
We never prune your list on our own. The action is always yours: “didn’t open” is a weak signal, and if your audience blocks tracking, automating this would delete a healthy list.
Lifecycle
Section titled “Lifecycle”A campaign’s status follows this flow:
draft │ ├─ /test (test send, does not change status) │ └─ /send ──► sending ──► sent │ ├─ /pause ──► paused │ │ └───────────────┘ /resume │ └─ /cancel ──► cancelled
/cancel also available from: draft, scheduled, pausedThe possible states are: draft, scheduled, sending, sent, paused,
cancelled. A campaign in sent is immutable — it cannot be resent.
Full example
Section titled “Full example”# 1. Create the campaigncurl -X POST https://api.mailerdash.com/v1/bulk/campaigns \ -H "Authorization: Bearer $MAILERDASH_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Newsletter Junio 2026", "template_id": "bienvenida-fc9674", "list_id": "newsletter-q3-2026-abc123", "from_email": "noticias@miempresa.com", "from_name": "Mi Empresa", "reply_to": "contacto@miempresa.com" }'
# 2. Send a test email to your inboxcurl -X POST https://api.mailerdash.com/v1/bulk/campaigns/cmp-d894cd3d626d9d11/test \ -H "Authorization: Bearer $MAILERDASH_API_KEY" \ -H "Content-Type: application/json" \ -d '{"to": "yo@miempresa.com"}'
# 3. Launch the bulk sendcurl -X POST https://api.mailerdash.com/v1/bulk/campaigns/cmp-d894cd3d626d9d11/send \ -H "Authorization: Bearer $MAILERDASH_API_KEY"Reference
Section titled “Reference”For the full request/response schema and error codes, see the bulk API reference.