EnvlopedDocs

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 draftscheduled / queuedsendingsent, with paused, canceled and failed as terminal detours.

Audience

GET  /v1/campaigns/:id/audience
PUT  /v1/campaigns/:id/audience

An 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_typeIn include modeIn exclude mode
listEveryone on the list who has not opted out of it…and remove them
segmentEveryone 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_ids is deprecated. It still works on campaign create and update and is treated as include-list sources, but it cannot express segments or exclusions. Prefer audience. When both are sent, audience wins.

Preview the audience before sending

GET /v1/campaigns/:id/audience/preview?limit=25

This 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" } }
  ]
}
FieldMeaning
includedSize of the union of include sources.
excludedHow many of those the exclude sources removed.
eligibleThe headline number: how many emails will actually go out.
breakdownWhy the rest will not be mailed.
sampleUp 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.

GateMeans
SubscriptionThe contact has not opted out (status).
DeliverabilityMail to them has not bounced, complained, or been suppressed.
EligibilityThe 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
ParameterTypeDescription
scheduled_atstringISO 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

StatuscodeMeaning
400no_include_sourceThe audience has no include source. Excludes alone say who not to mail and nothing about who to mail.
400empty_audienceThe audience resolves to zero sendable recipients. The response carries the full preview so you can see why. Nothing is written.
422unknown_fieldA segment in the audience references a field that has since been archived.
422invalid_segmentA segment in the audience is no longer valid.
422dead_sourceA segment in the audience has been deleted.
403domain_not_verifiedThe from_address domain is not verified for this account.
403marketing_profile_incompleteYour business address is required before any marketing send.
403reputation_suspendedSending is suspended for this account.
422missing_unsubscribeThe 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

MethodPathPurpose
GET/v1/campaignsPaginated list with live recipient counts.
POST/v1/campaignsCreate a draft. Accepts audience and template_id.
GET/v1/campaigns/:idDetail, including audience and counts.
PATCH/v1/campaigns/:idEdit a draft or scheduled campaign.
DELETE/v1/campaigns/:idDelete 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.

On this page