Skip to content

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.

List campaigns

GET /v1/bulk/campaigns

Optional parameters: status, limit (default 100), offset, key_id.

Create campaign

POST /v1/bulk/campaigns

Required body:

FieldTypeDescription
namestringInternal name of the campaign.
template_idstringID of the template to send.
list_idstringID of the target contact list.
from_emailstringSender 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.

Send or schedule

POST /v1/bulk/campaigns/{id}/send

Optional 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}/test

Required 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}/pause

Pauses a send in progress. Response: 200. Returns 409 if the campaign is not in sending.

Resume

POST /v1/bulk/campaigns/{id}/resume

Resumes a paused campaign. Response: 200. Returns 409 if the campaign is not in paused.

Cancel

POST /v1/bulk/campaigns/{id}/cancel

Cancels the campaign. Available from draft, scheduled, sending, or paused. Returns 409 if already in sent or cancelled.

Duplicate

POST /v1/bulk/campaigns/{id}/duplicate

Creates a new draft with the same parameters. Response: 201 with the new campaign object.

Detailed analytics

GET /v1/bulk/campaigns/{id}/analytics

Returns 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}/report

Delivery statistics for the campaign.

Deliverability

GET /v1/bulk/campaigns/{id}/deliverability

Crosses sends + bounces + complaints. Includes delivery_rate, bounce_rate, complaint_rate and a breakdown by destination domain.

Cross-campaign performance

GET /v1/bulk/campaigns/performance

Optional 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/rates

Optional parameters: period (24h | 7d | 30d, default 7d), key_id, format=csv. Returns period, granularity, window_start, totals, rates, and series[].

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}/sunset
POST /v1/bulk/campaigns/{id}/sunset

The 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.

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, paused

The possible states are: draft, scheduled, sending, sent, paused, cancelled. A campaign in sent is immutable — it cannot be resent.

Ventana de terminal
# 1. Create the campaign
curl -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 inbox
curl -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 send
curl -X POST https://api.mailerdash.com/v1/bulk/campaigns/cmp-d894cd3d626d9d11/send \
-H "Authorization: Bearer $MAILERDASH_API_KEY"

For the full request/response schema and error codes, see the bulk API reference.