Skip to contentDevelopersPrezly website
Contacts and outreach

Email campaigns

Prepare a campaign draft, review its audience and warnings, then test, send, or schedule deliberately.

Use an email campaign to send a reviewed message to a selected audience. A story and a campaign have separate lifecycles: publishing a story does not email it, and creating a campaign does not submit it for sending.

Keep preparation, recipient selection, testing, and the final send as separate steps. The requests below are alternatives at decision points, not a script to run from top to bottom. Test and send requests deliver real email when run against Prezly.

Choose credentials for the complete workflow

Load your credential into PREZLY_API_TOKEN. This variable can hold either credential type; its name does not make an integration key into a personal token.

OperationPersonal token access
Read/search campaignscampaigns:read
Create/update/delete a draftcampaigns:write
Inspect recipientscampaigns:read and contacts:read
Add/remove recipientscampaigns:write and contacts:read
Send immediatelycampaigns:send
Sender identity lookup, test, schedule/unschedule, duplicate, report export, job pollingIntegration key required; these endpoints are not available to personal tokens.

The owner also needs the corresponding email/contact permissions. Sending needs send_emails and available sending allowance; draft-management access alone does not authorize sending. Use an integration key for an unattended workflow that must test, schedule, or await background jobs.

Find existing work and choose a sender

List Campaigns returns an array under campaigns, plus pagination and sort. Search Campaigns accepts JSON-body search parameters. Store returned numeric campaign IDs; do not create a new draft for every retry of the same integration task.

Choose an approved sender identity for the intended account/user. With an integration key:

curl --fail-with-body --silent --show-error 'https://api.prezly.com/v2/sender-addresses' \
  --header "Authorization: Bearer $PREZLY_API_TOKEN" \
  --header 'Accept: application/json'

The response contains senders. Select the intended numeric sender ID and inspect its name, address, and verification/approval state; do not silently choose the first entry. Get Sender Address retrieves one identity. For creation, defaults, and domain readiness, see Manage email senders. With a personal token, use an already known sender ID configured in Prezly instead of attempting this lookup.

Create a draft with explicit content

Save this as campaign-draft.json, replacing both illustrative numeric IDs. Use the selected sender and the numeric id from Get Story for the story you intend to attach:

{
  "subject": "Acme opens its new studio",
  "sender": 34567,
  "story": { "id": 23456 },
  "content": { "text": "Hello,\n\nHere is our announcement about the new studio. Please let me know if you would like more information." }
}
curl --fail-with-body --silent --show-error --request POST 'https://api.prezly.com/v2/campaigns' \
  --header "Authorization: Bearer $PREZLY_API_TOKEN" \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --data-binary @campaign-draft.json

Without an initial recipient selector, successful creation returns 201 Created with campaign, warnings, message, and notifications. Persist campaign.id. Add recipients separately using Campaign audiences; initial bulk selections can instead defer creation work with a 202 job response.

Campaigns accept content: { text: "…" } as plain text, or serialized Prezly email content. Do not send raw HTML or reuse an entire story document as an email body. An attached story is a separate slot; confirm its visibility, public link, and release timing before sending.

Create Campaign →

Read and edit while it is a draft

Replace YOUR_CAMPAIGN_ID with the ID you saved:

curl --fail-with-body --silent --show-error 'https://api.prezly.com/v2/campaigns/YOUR_CAMPAIGN_ID' \
  --header "Authorization: Bearer $PREZLY_API_TOKEN" \
  --header 'Accept: application/json'

Inspect campaign.lifecycle_status, subject, sender, attached story, and warnings. Keep edits in the draft phase. Unschedule a scheduled campaign before editing it; do not edit an email while its delivery is already in flight.

A partial draft update can change just the subject:

curl --fail-with-body --silent --show-error --request PATCH 'https://api.prezly.com/v2/campaigns/YOUR_CAMPAIGN_ID' \
  --header "Authorization: Bearer $PREZLY_API_TOKEN" \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --data '{"subject":"Acme studio announcement — press information"}'

The response contains campaign and warnings. Fix blocking issues and review advisory warnings too. An accepted draft update is not a guarantee that its content, sender, story, or audience is ready to send. A fresh read does not lock the draft against concurrent editors.

Get Campaign → · Update Campaign →

Test before sending

Review the complete audience, including selected addresses, duplicates, bounces, and unsubscriptions. With an integration key, send a test to an address you control, replacing the example address:

curl --fail-with-body --silent --show-error --request POST 'https://api.prezly.com/v2/campaigns/YOUR_CAMPAIGN_ID/test' \
  --header "Authorization: Bearer $PREZLY_API_TOKEN" \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --data '{"email_addresses":["reviewer@example.test"]}'

This sends real test email; it is not a validation-only request. Inspect the rendered subject, sender, body, attached story/link, and presentation in the receiving client. A test does not approve the campaign's production recipient selection.

The response is 202 Accepted with a job reference. Await that job with an integration key, and inspect the received message before making the final decision.

Test Campaign →

Send now or schedule for later

After a final read and review, submit an immediate send once:

curl --fail-with-body --silent --show-error --request POST 'https://api.prezly.com/v2/campaigns/YOUR_CAMPAIGN_ID/send' \
  --header "Authorization: Bearer $PREZLY_API_TOKEN" \
  --header 'Accept: application/json'

A 202 Accepted response means the send was submitted to asynchronous processing; it does not mean every email was delivered. Save the campaign ID and returned progress.id. Do not automatically repeat this mutation after a timeout or lost response.

For a later send, use Schedule instead of Send, with an integration key. Campaign scheduling uses schedule_at, which is different from Story scheduling's publish_at. Prepare a future UTC minute with Node.js 22+; save as prepare-campaign-schedule.mjs and run it:

import { writeFile } from 'node:fs/promises';

const release = new Date(Date.now() + 24 * 60 * 60 * 1000);
release.setUTCSeconds(0, 0);
const payload = { schedule_at: release.toISOString() };
await writeFile('campaign-schedule.json', JSON.stringify(payload, null, 2));
console.log(JSON.stringify(payload, null, 2));

This writes a file only. Review/replace the generated time with your intended future send time, using Z or an explicit numeric offset. The server drops seconds; leave enough time that truncation does not put the schedule in the past. Check the attached story's availability at that time.

curl --fail-with-body --silent --show-error --request POST 'https://api.prezly.com/v2/campaigns/YOUR_CAMPAIGN_ID/schedule' \
  --header "Authorization: Bearer $PREZLY_API_TOKEN" \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --data-binary @campaign-schedule.json

The response contains campaign. Re-read lifecycle_status and scheduled_at to confirm the plan. To cancel a scheduled send before processing begins, use an integration key:

curl --fail-with-body --silent --show-error --request POST 'https://api.prezly.com/v2/campaigns/YOUR_CAMPAIGN_ID/unschedule' \
  --header "Authorization: Bearer $PREZLY_API_TOKEN" \
  --header 'Accept: application/json'

Unschedule returns the campaign to draft. It cannot recall sent messages or cancel a send already being processed. Unpublishing a linked story can also reset linked unsent campaigns, so coordinate story release and email scheduling rather than treating them as independent background jobs.

Send Campaign → · Schedule → · Unschedule →

Export a campaign report

With an integration key, Export Campaign Report starts a CSV export through POST /campaigns/{campaign_id}/reports/{view}/export. Use the saved numeric campaign ID and choose a documented view such as delivered or opened. Personal tokens cannot start this export or poll its job.

The response is 202 Accepted with progress, not the CSV file. Retain progress.id and use the Jobs workflow below. On successful resolution, the job result supplies download_url; treat that temporary link and the exported contact data as private. Handle a rejected job explicitly rather than assuming acceptance means a report is ready.

Reconcile accepted work and failures

With an integration key, replace YOUR_JOB_UUID with progress.id:

curl --fail-with-body --silent --show-error 'https://api.prezly.com/v2/jobs/YOUR_JOB_UUID' \
  --header "Authorization: Bearer $PREZLY_API_TOKEN" \
  --header 'Accept: application/json'

The response contains job; the nested job.state.state is pending, resolved, or rejected. Use bounded polling with delays while pending, handle rejection, then re-read the campaign. A completed job is not proof of inbox delivery to every recipient. Campaign lifecycle_status and delivery statistics are different views of progress; sent_at alone is not a delivery receipt.

Personal tokens can read campaign state but cannot call Jobs. Reconcile through campaign reads and the Prezly UI, or use an integration key for a workflow that needs job completion.

On an ambiguous send result, read and reconcile the known campaign before doing anything else. Do not create a replacement campaign or repeat Send to find out whether the first request worked. For 403, check endpoint eligibility, owner permissions, and sending allowance; for validation/conflict errors, inspect the reported fields and warnings. See Handling errors.

Duplicate Campaign creates another draft; Delete Campaign removes a draft. Neither is a recovery mechanism for an uncertain send. Use individual pitches when writing separately to one contact.

On this page