Campaigns
Create marketing campaigns, target them with include and exclude audiences, preview and send
Campaigns
A campaign is a marketing email plus an audience. The audience is everyone in the campaign's include sources, minus everyone in its exclude sources, resolved at the moment the send starts.
All endpoints require an Authorization: Bearer YOUR_API_KEY header.
The campaign object
{
"id": "cp_abc123",
"name": "August Singapore edition",
"from_address": "Never Strangers <hello@example.com>",
"reply_to": null,
"subject": "Saturday in Singapore",
"status": "draft",
"scheduled_at": null,
"targeted": 0,
"sent_count": 0,
"skipped_count": 0,
"audience": {
"include": [{ "source_type": "segment", "source_id": "sg_abc123" }],
"exclude": [{ "source_type": "campaign", "source_id": "cp_lastweek" }]
},
"list_ids": [],
"created_at": "2026-08-09T10:30:00Z",
"updated_at": "2026-08-09T10:30:00Z"
}status moves through draft → scheduled / queued → sending → sent,
with paused, canceled and failed as terminal detours.
Audience
GET /v1/campaigns/:id/audience
PUT /v1/campaigns/:id/audienceAn audience is two lists of sources:
{
"include": [
{ "source_type": "segment", "source_id": "sg_singapore_25_35" },
{ "source_type": "list", "source_id": "ls_vip" }
],
"exclude": [
{ "source_type": "segment", "source_id": "sg_already_has_ticket" },
{ "source_type": "campaign", "source_id": "cp_three_days_ago" }
]
}source_type | In include mode | In exclude mode |
|---|---|---|
list | Everyone on the list who has not opted out of it | …and remove them |
segment | Everyone matching the segment right now | …and remove them |
campaign | (not typical) | Everyone a past campaign was actually sent to |
Exclusion is not the inverse of a filter — the two sides are built from different criteria. "Send to Manila, but not to anyone we emailed three days ago" is one include and one exclude, and the exclude is a campaign.
PUT replaces the audience wholesale and returns what was stored. Sources that
do not exist, are deleted, or belong to another account are dropped and counted
in dropped_sources.
curl -X PUT https://api.envloped.com/v1/campaigns/cp_abc123/audience \
-H "Authorization: Bearer en_abc123..." \
-H "Content-Type: application/json" \
-d '{
"audience": {
"include": [{ "source_type": "segment", "source_id": "sg_singapore_25_35" }],
"exclude": [{ "source_type": "campaign", "source_id": "cp_three_days_ago" }]
}
}'
list_idsis deprecated. It still works on campaign create and update and is treated as include-list sources, but it cannot express segments or exclusions. Preferaudience. When both are sent,audiencewins.
Preview the audience before sending
GET /v1/campaigns/:id/audience/preview?limit=25This is the number to show a human before a send, and the sample to let them sanity-check it.
{
"included": 4210,
"excluded": 388,
"eligible": 3822,
"breakdown": {
"ineligible": 0,
"unsubscribed": 41,
"suppressed": 12,
"bounced": 3,
"complained": 0
},
"sample": [
{ "id": "ct_1", "email": "jane@example.com", "first_name": "Jane",
"last_name": null, "status": "subscribed",
"attributes": { "city": "Singapore", "date_of_birth": "1996-04-12" } }
]
}| Field | Meaning |
|---|---|
included | Size of the union of include sources. |
excluded | How many of those the exclude sources removed. |
eligible | The headline number: how many emails will actually go out. |
breakdown | Why the rest will not be mailed. |
sample | Up to 100 real contacts from the eligible set. |
The identity included − excluded = eligible + sum(breakdown) always holds, so
every contact the audience pulled in is accounted for.
This endpoint runs the same resolution the send does, so the preview cannot disagree with the send.
The three gates
Before an address is mailed it must clear all three. A segment cannot override any of them.
| Gate | Means |
|---|---|
| Subscription | The contact has not opted out (status). |
| Deliverability | Mail to them has not bounced, complained, or been suppressed. |
| Eligibility | The contact is allowed to be mailed at all (eligibility) — see Contacts. |
Contacts blocked by a gate are still recorded against the campaign as skipped
with a reason, so the report's funnel stays honest.
Send or schedule
POST /v1/campaigns/:id/send| Parameter | Type | Description |
|---|---|---|
scheduled_at | string | ISO 8601, at least a minute in the future. Omit to send now. |
A scheduled campaign resolves its audience when it fires, not when it was scheduled. A campaign scheduled on Friday for Monday 10am sends to whoever matches on Monday at 10am.
curl -X POST https://api.envloped.com/v1/campaigns/cp_abc123/send \
-H "Authorization: Bearer en_abc123..." \
-H "Content-Type: application/json" \
-d '{ "scheduled_at": "2026-08-11T02:00:00Z" }'Send errors
| Status | code | Meaning |
|---|---|---|
400 | no_include_source | The audience has no include source. Excludes alone say who not to mail and nothing about who to mail. |
400 | empty_audience | The audience resolves to zero sendable recipients. The response carries the full preview so you can see why. Nothing is written. |
422 | unknown_field | A segment in the audience references a field that has since been archived. |
422 | invalid_segment | A segment in the audience is no longer valid. |
422 | dead_source | A segment in the audience has been deleted. |
403 | domain_not_verified | The from_address domain is not verified for this account. |
403 | marketing_profile_incomplete | Your business address is required before any marketing send. |
403 | reputation_suspended | Sending is suspended for this account. |
422 | missing_unsubscribe | The message has no unsubscribe link even after footer injection. |
A dead_source or unknown_field is deliberately a hard failure rather than a
silent drop: dropping an include under-sends and dropping an exclude over-sends,
and neither is ours to guess.
Other endpoints
| Method | Path | Purpose |
|---|---|---|
GET | /v1/campaigns | Paginated list with live recipient counts. |
POST | /v1/campaigns | Create a draft. Accepts audience and template_id. |
GET | /v1/campaigns/:id | Detail, including audience and counts. |
PATCH | /v1/campaigns/:id | Edit a draft or scheduled campaign. |
DELETE | /v1/campaigns/:id | Delete a non-active campaign. |
Merge tags
Subject and body support {{ key }}, resolving against the contact's
first_name, last_name, email, and every registered
contact field. Unknown tags render as empty rather than
leaking the raw tag to a recipient.
Every campaign automatically gets a compliant footer with your business address
and a one-click unsubscribe link, plus RFC 8058 List-Unsubscribe headers.