Campaigns
List, manage, send, and report on email campaigns. Paths are relative to https://app.blacklistguard.com/api/v1 and require your X-API-Key header.
List campaigns
GET /campaigns
Query parameters: page, limit. Returns a campaigns array plus a pagination object. Each campaign carries its own counts and rates: sent_count, open_rate, bounce_count, mailing_list_name, and so on.
Get a campaign
GET /campaigns/{id}
Returns the full campaign, including its content, delivery settings, and current stats.
Create & update
POST /campaigns
PUT /campaigns/{id}
Both take the campaign object as the JSON body. PUT is a full-object update. To test versions against each other, add an ab_test. A new campaign is created as a draft; start delivery with POST /campaigns/{id}/send.
Core fields
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | Yes | Internal campaign name. |
subject | string | Yes | Subject line. |
from_email | string | Yes | Must be on a verified domain or a verified sender, else 400. |
from_name | string | No | Sender display name. |
reply_to | string | No | Reply-to address. |
message_type | string | No | email (default) or sms. |
phone_field_name | string | SMS only | Subscriber field holding the phone number. |
Targeting
| Field | Type | Notes |
|---|---|---|
recipient_type | string | mailing_list, segment, or custom. Auto-detected if omitted (segment_id → segment, else segment_conditions → custom, else mailing list). |
mailing_list_id | uuid | A single list (multiple lists aren't supported). Validated when you send, not on create. |
segment_id | uuid | Used when recipient_type is segment. |
segment_conditions | JSON | Inline segment rules (see segment operators). Required when recipient_type is custom. A portion rule saved without a key gets one, returned in the campaign's segment_conditions. |
Content
| Field | Type | Notes |
|---|---|---|
html_content | string | HTML body. |
text_content | string | Plain-text body. |
Tracking & delivery
| Field | Type | Notes |
|---|---|---|
enable_open_tracking | bool | Omit to inherit the list's default. |
enable_click_tracking | bool | Omit to inherit the list's default. |
url_domain_id | uuid | Tracking domain for links. |
use_custom_headers | bool | Enable the raw header block below. |
custom_headers | string | Raw headers, used when use_custom_headers is true. |
virtual_mta_ip | string | Where the campaign sends from. One of your dedicated IPs, routing_rule:<uuid> for a routing rule, or shared_pool to always use the shared pool. Leave it empty to follow the mailing list's setting; if the list doesn't set one either, your dedicated IP is used if you have one, otherwise the shared pool. |
routing_rule_id | uuid | Returned when you read a campaign that uses a routing rule. To choose a rule, set virtual_mta_ip to routing_rule:<uuid>; this field is ignored when you create or update. |
bounce_email | string | Bounce address. Works the same as on Send an email: leave it out and BlacklistGuard handles bounces for you. |
sender_email | string | Envelope sender override. |
Scheduling & attachments
| Field | Type | Notes |
|---|---|---|
scheduled_at | datetime | Interpreted in your workspace's timezone, stored as UTC. Dispatch still happens via the scheduler or /send. |
attachments | array | Same shape as Send an email: { filename, content_type, content, disposition, content_id }. |
mailing_list_id, or a segment / custom conditions). To test versions of an email, see A/B tests.The response is the created or updated campaign. Read back its id, status (draft on create), recipient_type, total_recipients, timestamps, and the stat fields (sent_count, open_count, rates, all zero until it sends). status, sent_at, and the statistics are system-managed and read-only.
PUT returns 409 with code CONFLICT. Duplicate it into a new draft to make changes.Send, pause & resume
POST /campaigns/{id}/send
POST /campaigns/{id}/pause
POST /campaigns/{id}/resume
send begins delivery to the campaign's audience. pause halts an in-progress send; resume continues it.
If your plan has a daily sending limit and the campaign has more recipients than you have left for the day when it's due to go out, nothing is sent. The campaign goes back to draft and your workspace admins get an email. Schedule it for after 00:00 UTC, when the daily count resets.
If sending is paused for your account, send returns 403 with the code SENDING_PAUSED, and the same applies to triggering an autoresponder. Contact support to have it reviewed.
A/B tests
An email campaign can test 2 or 3 versions against each other. Part of the list gets one version each, and after a wait the best one goes to everyone else. Add an ab_test object when you create or update the campaign:
"ab_test": {
"test_percentage": 20,
"wait_hours": 4,
"winner_metric": "clicks",
"versions": [
{ "name": "Short subject", "subject": "Our sale starts today", "html_content": "<p>…</p>", "text_content": "…" },
{ "name": "Question", "subject": "Ready for the sale?", "html_content": "<p>…</p>", "text_content": "…" }
]
}
| Field | Type | Description |
|---|---|---|
test_percentage | integer | How much of the list is in the test, from 10 to 100. At 100 it's a plain split: everyone gets one of the versions, and there's no wait and no winner. |
wait_hours | integer | How long to wait after the test group is sent before picking the winner, from 1 to 120. Required below 100. |
winner_metric | string | clicks (the default), opens, or manual if you want to pick the winner yourself. A tie, or no data at all, goes to version 1. |
versions | array | 2 or 3 versions, in the order you want them shown. Each needs a subject. name defaults to "Version 1", "Version 2" and so on, and html_content and text_content are optional. |
Version 1 becomes the campaign's own subject and content, whatever the top-level fields say. You still need the usual name and from_email. Because PUT replaces the whole campaign, send ab_test again on every update: leaving it out turns the campaign back into a regular one. A/B tests work for email campaigns only.
A value that isn't valid returns 400 with a message saying which one, for example An A/B test needs 2 or 3 versions or Version 2 needs a subject.
Following a test
Every campaign has an ab_test field, which is null for a regular campaign. Getting a single campaign includes each version with its results: sent_count (delivered), open_count and click_count (unique people), open_rate and click_rate (percentages of sent_count), bounce_count, and is_winner. The versions add up to the campaign's own figures. The campaign list has the test's settings and state, but not the versions.
The test's progress is in phase, which is separate from the campaign's status:
null: not sent yet.split_testing: the test group is going out.decision_delay: waiting before the winner is picked.decide_atsays when the wait ends.finished: a winner was sent, a plain split finished, the test was cancelled, or a manual test passed its deadline.
For a manual test, pick_winner_by gives your deadline while the test runs, 7 days after the wait ends. If you don't pick by then, the recipients still waiting don't get the campaign. winner_version_id names the winner once there is one.
Pausing the campaign also pauses the test's clock, so decide_at moves later by the time it was paused. Cancelling the rest of the queue ends the test with no winner.
Picking the winner yourself
POST /campaigns/{id}/ab-test/winner
{ "version_id": "6ebb82e4-93fe-444f-b582-2f5199b4a37d" }
Use this to choose the winner of a manual test, or to end an automatic test early. It works while the test is in split_testing or decision_delay, but not on a plain split. The chosen version goes to everyone still waiting, the test finishes at once, and the response is the full campaign. If the test isn't running, or the version isn't part of this campaign, you get a 400 that says so.
Autoresponder triggers
POST /autoresponders
PUT /autoresponders/{id}
Autoresponders take the same content and delivery fields as a campaign, plus a trigger_type, a delay (delay_type and delay_value), and whatever the trigger needs:
trigger_type | Needs |
|---|---|
subscription, api | Nothing else. api sends only through POST /autoresponders/{id}/trigger. |
campaign_open, campaign_click, campaign_sent | trigger_campaign_id: a campaign on the same mailing list. |
autoresponder_open, autoresponder_click, autoresponder_sent | trigger_autoresponder_id: another autoresponder on the same mailing list. |
autoresponder_not_open, autoresponder_not_click | trigger_autoresponder_id, and a delay that isn't immediate. The delay is how long to wait for the open or click. Both autoresponders must be email. |
date_based | trigger_config as below, with delay_type set to immediate. |
Send only the reference the trigger uses: trigger_campaign_id for campaign triggers, trigger_autoresponder_id for autoresponder triggers, and neither for the rest.
trigger_config is {} for every trigger except these two:
- Click triggers (
campaign_click,autoresponder_click,autoresponder_not_click) can take{ "links": ["https://shop.example/buy"] }, so only clicks on those links count. Leavelinksout, or send[], for any link. Each link has to appear in the content you're following exactly as written there. - Date-based takes
{ "date_field": "renewal_date", "date_offset": -3, "repeat": "once", "send_hour": 9 }.date_fieldis the name of a custom field of type date and is required.date_offsetis whole days from -365 to 365 (default 0).repeatisyearly(the default) oronce.send_houris 0 to 23 in New York time (default 9).
The response includes trigger_config with the defaults filled in. A setup that can't work, like a trigger campaign on another list or a link that isn't in the content, returns 400 with a message that says what's wrong. See Autoresponder triggers for how each one behaves.
Sending settings
GET /sending-settings
PUT /sending-settings { "unsubscribe_footer": false }
Returns { unsubscribe_footer }, which is true unless you've changed it. While it's true, any campaign or autoresponder email whose HTML or text has neither {{.UnsubscribeURL}} nor {{.UnsubscribeLink}} gets an Unsubscribe link added at the bottom when it's sent. Set it to false to stop that. PUT needs unsubscribe_footer as a boolean, or it returns 400. With an API key, these need the sending-settings permission. See Unsubscribe links.
Delete
DELETE /campaigns/{id}
Analytics
GET /campaigns/{id}/activity
GET /campaigns/{id}/smtp-domain-stats
GET /campaigns/{id}/messages
GET /campaigns/{id}/recent-activity
- activity: hourly cumulative series:
[{ time, sent, opens, clicks, unsubscribes, complaints }]. - smtp-domain-stats: delivery broken down by recipient domain:
{ pagination, stats:[{ domain, total_messages, percent_total, success, bounced, failure, still_trying }] }. - messages: per-recipient results:
{ messages:[{ id, time, localpart, domain, status, attempts, error_message }], pagination }. Filter withstatus,email,page,limit. - recent-activity: per-event feed:
{ activities:[{ id, type, email, description, timestamp }], pagination }wheretypeissent|opened|clicked|bounced|unsubscribed|complained.