Newsletter Campaigns
Create campaigns, manage recipients, queue sends, inspect stats, and control suppression lists.
Newsletter API
The Newsletter API exposes campaign lifecycle, recipient synchronization, dispatch queue controls, analytics, and suppression list management.
Operational Validation Behavior
Preflight and compliance analysis can be attached as advisory diagnostics in send/test flows. These diagnostics improve quality but do not hard-block operational newsletter dispatch by themselves.
List Newsletter Campaigns
newsletters:readPath, query, and header parameters
sizepageResponses
Newsletter list returned.
{
"entities": "Newsletter",
"count": 1,
"per_page": 100,
"pages": {
"current": 1,
"max": 1
},
"elements": [
{
"id": "672489...",
"title": "Launch Campaign",
"subject": "New release",
"template_id": "welcome.html",
"total_recipients": 210,
"status": "draft",
"created_at": 1704067200
}
]
}Request examples
curl --request GET 'https://api.hub.donutwork.com/2026-02-01/newsletters.json' \
--header 'Authorization: Bearer YOUR_API_KEY'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/newsletters.json', {
method: 'GET',
headers: {
Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
},
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
const error = await response.text();
throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
: contentType.includes('application/json') ? await response.json()
: await response.text();$request = curl_init('https://api.hub.donutwork.com/2026-02-01/newsletters.json');
curl_setopt_array($request, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
],
]);
$response = curl_exec($request);
if ($response === false) {
$error = curl_error($request);
curl_close($request);
throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
: (str_contains($contentType, 'application/json')
? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.
Create Newsletter Campaign
newsletters:writePath, query, and header parameters
No path, query, or additional header parameters.
Request Body
{
"newsletter": {
"title": "Launch Campaign",
"subject": "New release",
"template_id": "welcome.html",
"contact_ids": [
"cust_1",
"cust_2"
]
}
}newsletter.titleRequirednewsletter.subjectRequirednewsletter.template_idRequirednewsletter.contact_idsResponses
Newsletter draft created.
{
"id": "672489...",
"status": "draft",
"skipped_suppressed": 0,
"skipped_not_found": 1
}Request examples
curl --request POST 'https://api.hub.donutwork.com/2026-02-01/newsletters.json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"newsletter": {
"title": "Launch Campaign",
"subject": "New release",
"template_id": "welcome.html",
"contact_ids": [
"cust_1",
"cust_2"
]
}
}'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/newsletters.json', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"newsletter": {
"title": "Launch Campaign",
"subject": "New release",
"template_id": "welcome.html",
"contact_ids": [
"cust_1",
"cust_2"
]
}
}),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
const error = await response.text();
throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
: contentType.includes('application/json') ? await response.json()
: await response.text();$request = curl_init('https://api.hub.donutwork.com/2026-02-01/newsletters.json');
curl_setopt_array($request, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => '{
"newsletter": {
"title": "Launch Campaign",
"subject": "New release",
"template_id": "welcome.html",
"contact_ids": [
"cust_1",
"cust_2"
]
}
}',
]);
$response = curl_exec($request);
if ($response === false) {
$error = curl_error($request);
curl_close($request);
throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
: (str_contains($contentType, 'application/json')
? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.
Retrieve Campaign Details
newsletters:readPath, query, and header parameters
newsletterIdRequiredResponses
Campaign details returned.
{
"id": "672489...",
"title": "Launch Campaign",
"subject": "New release",
"template_id": "welcome.html",
"total_recipients": 210,
"status": "queued",
"scheduled_at": null,
"created_at": 1704067200
}Request examples
curl --request GET 'https://api.hub.donutwork.com/2026-02-01/newsletters/NEWSLETTERID.json' \
--header 'Authorization: Bearer YOUR_API_KEY'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/newsletters/NEWSLETTERID.json', {
method: 'GET',
headers: {
Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
},
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
const error = await response.text();
throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
: contentType.includes('application/json') ? await response.json()
: await response.text();$request = curl_init('https://api.hub.donutwork.com/2026-02-01/newsletters/NEWSLETTERID.json');
curl_setopt_array($request, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
],
]);
$response = curl_exec($request);
if ($response === false) {
$error = curl_error($request);
curl_close($request);
throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
: (str_contains($contentType, 'application/json')
? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.
Update Campaign Metadata
newsletters:writePath, query, and header parameters
newsletterIdRequiredRequest Body
{
"newsletter": {
"title": "Launch Campaign v2",
"subject": "Release update",
"template_id": "welcome-v2.html"
}
}newsletter.titlenewsletter.subjectnewsletter.template_idResponses
Campaign metadata updated.
{
"id": "672489...",
"title": "Launch Campaign v2",
"subject": "Release update",
"template_id": "welcome-v2.html",
"total_recipients": 210,
"status": "draft",
"scheduled_at": null,
"created_at": 1704067200
}Campaign is not in draft status.
{
"error": "Only newsletters in draft status can be updated"
}Request examples
curl --request PUT 'https://api.hub.donutwork.com/2026-02-01/newsletters/NEWSLETTERID.json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"newsletter": {
"title": "Launch Campaign v2",
"subject": "Release update",
"template_id": "welcome-v2.html"
}
}'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/newsletters/NEWSLETTERID.json', {
method: 'PUT',
headers: {
Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"newsletter": {
"title": "Launch Campaign v2",
"subject": "Release update",
"template_id": "welcome-v2.html"
}
}),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
const error = await response.text();
throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
: contentType.includes('application/json') ? await response.json()
: await response.text();$request = curl_init('https://api.hub.donutwork.com/2026-02-01/newsletters/NEWSLETTERID.json');
curl_setopt_array($request, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => '{
"newsletter": {
"title": "Launch Campaign v2",
"subject": "Release update",
"template_id": "welcome-v2.html"
}
}',
]);
$response = curl_exec($request);
if ($response === false) {
$error = curl_error($request);
curl_close($request);
throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
: (str_contains($contentType, 'application/json')
? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.
Delete Campaign
newsletters:writePath, query, and header parameters
newsletterIdRequiredResponses
Campaign and related queue entries deleted.
{
"id": "672489...",
"deleted": true
}Request examples
curl --request DELETE 'https://api.hub.donutwork.com/2026-02-01/newsletters/NEWSLETTERID.json' \
--header 'Authorization: Bearer YOUR_API_KEY'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/newsletters/NEWSLETTERID.json', {
method: 'DELETE',
headers: {
Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
},
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
const error = await response.text();
throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
: contentType.includes('application/json') ? await response.json()
: await response.text();$request = curl_init('https://api.hub.donutwork.com/2026-02-01/newsletters/NEWSLETTERID.json');
curl_setopt_array($request, [
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
],
]);
$response = curl_exec($request);
if ($response === false) {
$error = curl_error($request);
curl_close($request);
throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
: (str_contains($contentType, 'application/json')
? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.
Get Campaign Recipients Snapshot
newsletters:readPath, query, and header parameters
newsletterIdRequiredsearchResponses
Recipients snapshot returned.
{
"newsletter_status": "draft",
"selected_ids": [
"cust_1",
"cust_2"
],
"all_customers": [
{
"id": "cust_1",
"name": "Alex Doe",
"email": "alex@example.com",
"tags": [
"beta"
],
"subscription_count": 1,
"has_consent": true,
"is_suppressed": false
}
]
}Request examples
curl --request GET 'https://api.hub.donutwork.com/2026-02-01/newsletters/NEWSLETTERID/recipients.json' \
--header 'Authorization: Bearer YOUR_API_KEY'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/newsletters/NEWSLETTERID/recipients.json', {
method: 'GET',
headers: {
Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
},
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
const error = await response.text();
throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
: contentType.includes('application/json') ? await response.json()
: await response.text();$request = curl_init('https://api.hub.donutwork.com/2026-02-01/newsletters/NEWSLETTERID/recipients.json');
curl_setopt_array($request, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
],
]);
$response = curl_exec($request);
if ($response === false) {
$error = curl_error($request);
curl_close($request);
throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
: (str_contains($contentType, 'application/json')
? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.
Set Campaign Recipients
newsletters:writePath, query, and header parameters
newsletterIdRequiredRequest Body
{
"newsletter": {
"contact_ids": [
"cust_1",
"cust_2",
"cust_3"
]
}
}newsletter.contact_idsRequiredResponses
Recipients synchronized.
{
"newsletter_id": "672489...",
"saved_recipients": 2,
"skipped_suppressed": 1,
"skipped_not_found": 0
}Request examples
curl --request PUT 'https://api.hub.donutwork.com/2026-02-01/newsletters/NEWSLETTERID/recipients.json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"newsletter": {
"contact_ids": [
"cust_1",
"cust_2",
"cust_3"
]
}
}'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/newsletters/NEWSLETTERID/recipients.json', {
method: 'PUT',
headers: {
Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"newsletter": {
"contact_ids": [
"cust_1",
"cust_2",
"cust_3"
]
}
}),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
const error = await response.text();
throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
: contentType.includes('application/json') ? await response.json()
: await response.text();$request = curl_init('https://api.hub.donutwork.com/2026-02-01/newsletters/NEWSLETTERID/recipients.json');
curl_setopt_array($request, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => '{
"newsletter": {
"contact_ids": [
"cust_1",
"cust_2",
"cust_3"
]
}
}',
]);
$response = curl_exec($request);
if ($response === false) {
$error = curl_error($request);
curl_close($request);
throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
: (str_contains($contentType, 'application/json')
? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.
Queue Campaign Dispatch
newsletters:writePath, query, and header parameters
newsletterIdRequiredResponses
Campaign queued for sending.
{
"newsletter_id": "672489...",
"status": "queued",
"queued_recipients": 150,
"skipped_suppressed": 0,
"skipped_not_found": 0
}Invalid state or usage limit reached.
{
"error": "This newsletter is already queued and cannot be queued again"
}Request examples
curl --request POST 'https://api.hub.donutwork.com/2026-02-01/newsletters/NEWSLETTERID/queue.json' \
--header 'Authorization: Bearer YOUR_API_KEY'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/newsletters/NEWSLETTERID/queue.json', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
},
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
const error = await response.text();
throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
: contentType.includes('application/json') ? await response.json()
: await response.text();$request = curl_init('https://api.hub.donutwork.com/2026-02-01/newsletters/NEWSLETTERID/queue.json');
curl_setopt_array($request, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
],
]);
$response = curl_exec($request);
if ($response === false) {
$error = curl_error($request);
curl_close($request);
throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
: (str_contains($contentType, 'application/json')
? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.
Send Test Email
newsletters:writePath, query, and header parameters
newsletterIdRequiredRequest Body
{
"newsletter": {
"test_email": "qa@example.com"
}
}newsletter.test_emailRequiredResponses
Test email sent.
{
"newsletter_id": "672489...",
"test_email": "qa@example.com",
"sent": true
}Request examples
curl --request POST 'https://api.hub.donutwork.com/2026-02-01/newsletters/NEWSLETTERID/test.json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"newsletter": {
"test_email": "qa@example.com"
}
}'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/newsletters/NEWSLETTERID/test.json', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"newsletter": {
"test_email": "qa@example.com"
}
}),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
const error = await response.text();
throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
: contentType.includes('application/json') ? await response.json()
: await response.text();$request = curl_init('https://api.hub.donutwork.com/2026-02-01/newsletters/NEWSLETTERID/test.json');
curl_setopt_array($request, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => '{
"newsletter": {
"test_email": "qa@example.com"
}
}',
]);
$response = curl_exec($request);
if ($response === false) {
$error = curl_error($request);
curl_close($request);
throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
: (str_contains($contentType, 'application/json')
? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.
Get Campaign Stats
newsletters:readPath, query, and header parameters
newsletterIdRequiredfailure_limitResponses
Campaign stats returned.
{
"newsletter": {
"id": "672489...",
"title": "Launch Campaign",
"subject": "New release",
"template_id": "welcome.html",
"total_recipients": 150,
"status": "queued",
"scheduled_at": null,
"created_at": 1704067200
},
"stats": {
"pending": 12,
"processing": 3,
"sent": 120,
"failed": 10,
"skipped": 5,
"total": 150
},
"progress": {
"processed": 135,
"total": 150,
"overall_progress": 90,
"delivery_rate": 80
},
"failures": []
}Request examples
curl --request GET 'https://api.hub.donutwork.com/2026-02-01/newsletters/NEWSLETTERID/stats.json' \
--header 'Authorization: Bearer YOUR_API_KEY'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/newsletters/NEWSLETTERID/stats.json', {
method: 'GET',
headers: {
Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
},
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
const error = await response.text();
throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
: contentType.includes('application/json') ? await response.json()
: await response.text();$request = curl_init('https://api.hub.donutwork.com/2026-02-01/newsletters/NEWSLETTERID/stats.json');
curl_setopt_array($request, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
],
]);
$response = curl_exec($request);
if ($response === false) {
$error = curl_error($request);
curl_close($request);
throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
: (str_contains($contentType, 'application/json')
? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.
Suppression List
List Unsubscribes
newsletter_unsubscribes:readPath, query, and header parameters
sizepageResponses
Suppression list returned.
{
"entities": "Unsubscribes",
"count": 1,
"per_page": 100,
"pages": {
"current": 1,
"max": 1
},
"elements": [
{
"email": "user@example.com",
"reason": "Manual unsubscribe via API",
"unsubscribed_at": 1704067200
}
]
}Request examples
curl --request GET 'https://api.hub.donutwork.com/2026-02-01/newsletters/unsubscribes.json' \
--header 'Authorization: Bearer YOUR_API_KEY'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/newsletters/unsubscribes.json', {
method: 'GET',
headers: {
Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
},
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
const error = await response.text();
throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
: contentType.includes('application/json') ? await response.json()
: await response.text();$request = curl_init('https://api.hub.donutwork.com/2026-02-01/newsletters/unsubscribes.json');
curl_setopt_array($request, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
],
]);
$response = curl_exec($request);
if ($response === false) {
$error = curl_error($request);
curl_close($request);
throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
: (str_contains($contentType, 'application/json')
? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.
Add Unsubscribe
newsletter_unsubscribes:writePath, query, and header parameters
No path, query, or additional header parameters.
Request Body
{
"unsubscribe": {
"email": "user@example.com",
"reason": "Support request"
}
}unsubscribe.emailRequiredunsubscribe.reasonResponses
Email added to suppression list.
{
"email": "user@example.com",
"unsubscribed": true
}Request examples
curl --request POST 'https://api.hub.donutwork.com/2026-02-01/newsletters/unsubscribes.json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"unsubscribe": {
"email": "user@example.com",
"reason": "Support request"
}
}'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/newsletters/unsubscribes.json', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"unsubscribe": {
"email": "user@example.com",
"reason": "Support request"
}
}),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
const error = await response.text();
throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
: contentType.includes('application/json') ? await response.json()
: await response.text();$request = curl_init('https://api.hub.donutwork.com/2026-02-01/newsletters/unsubscribes.json');
curl_setopt_array($request, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => '{
"unsubscribe": {
"email": "user@example.com",
"reason": "Support request"
}
}',
]);
$response = curl_exec($request);
if ($response === false) {
$error = curl_error($request);
curl_close($request);
throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
: (str_contains($contentType, 'application/json')
? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.
Resubscribe Email
newsletter_unsubscribes:writePath, query, and header parameters
No path, query, or additional header parameters.
Request Body
{
"unsubscribe": {
"email": "user@example.com"
}
}unsubscribe.emailRequiredResponses
Email removed from suppression list.
{
"email": "user@example.com",
"resubscribed": true
}Email not found in suppression list.
{
"error": "Email not found in suppression list"
}Request examples
curl --request POST 'https://api.hub.donutwork.com/2026-02-01/newsletters/unsubscribes/resubscribe.json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"unsubscribe": {
"email": "user@example.com"
}
}'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/newsletters/unsubscribes/resubscribe.json', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"unsubscribe": {
"email": "user@example.com"
}
}),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
const error = await response.text();
throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
: contentType.includes('application/json') ? await response.json()
: await response.text();$request = curl_init('https://api.hub.donutwork.com/2026-02-01/newsletters/unsubscribes/resubscribe.json');
curl_setopt_array($request, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => '{
"unsubscribe": {
"email": "user@example.com"
}
}',
]);
$response = curl_exec($request);
if ($response === false) {
$error = curl_error($request);
curl_close($request);
throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
: (str_contains($contentType, 'application/json')
? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.