Donutwork Docs

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.


List Newsletter Campaigns

GET
/2026-02-01/newsletters.json
Required permissionnewsletters:readApiAccessPermission::NEWSLETTERS_READ

Query Parameters

sizeinteger
Records per page (max 100).
pageinteger
Page index (starting from 1).

Responses

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
    }
  ]
}

Create Newsletter Campaign

POST
/2026-02-01/newsletters.json
Required permissionnewsletters:writeApiAccessPermission::NEWSLETTERS_WRITE

Query Parameters

No query parameters required.

Request Body

JSON
{
  "newsletter": {
    "title": "Launch Campaign",
    "subject": "New release",
    "template_id": "welcome.html",
    "contact_ids": [
      "cust_1",
      "cust_2"
    ]
  }
}
newsletter.titlestringRequired
Internal campaign name.
newsletter.subjectstringRequired
Subject line used during sending.
newsletter.template_idstringRequired
Template filename to use for this campaign.
newsletter.contact_idsarray
Optional initial recipients list.

Responses

Newsletter draft created.

{
  "id": "672489...",
  "status": "draft",
  "skipped_suppressed": 0,
  "skipped_not_found": 1
}

Retrieve Campaign Details

GET
/2026-02-01/newsletters/{newsletterId}.json
Required permissionnewsletters:readApiAccessPermission::NEWSLETTERS_READ

Query Parameters

newsletterIdstringRequired
Newsletter identifier.

Responses

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
}

Update Campaign Metadata

PUT
/2026-02-01/newsletters/{newsletterId}.json
Required permissionnewsletters:writeApiAccessPermission::NEWSLETTERS_WRITE

Query Parameters

newsletterIdstringRequired
Newsletter identifier.

Request Body

JSON
{
  "newsletter": {
    "title": "Launch Campaign v2",
    "subject": "Release update",
    "template_id": "welcome-v2.html"
  }
}
newsletter.titlestring
Updated title. Allowed only in draft status.
newsletter.subjectstring
Updated subject. Allowed only in draft status.
newsletter.template_idstring
Updated template id. Allowed only in draft status.

Responses

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"
}

Delete Campaign

DELETE
/2026-02-01/newsletters/{newsletterId}.json
Required permissionnewsletters:writeApiAccessPermission::NEWSLETTERS_WRITE

Query Parameters

newsletterIdstringRequired
Newsletter identifier.

Responses

Campaign and related queue entries deleted.

{
  "id": "672489...",
  "deleted": true
}

Get Campaign Recipients Snapshot

GET
/2026-02-01/newsletters/{newsletterId}/recipients.json
Required permissionnewsletters:readApiAccessPermission::NEWSLETTERS_READ

Query Parameters

newsletterIdstringRequired
Newsletter identifier.
searchstring
Optional search filter by customer name or email.

Responses

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
    }
  ]
}

Set Campaign Recipients

PUT
/2026-02-01/newsletters/{newsletterId}/recipients.json
Required permissionnewsletters:writeApiAccessPermission::NEWSLETTERS_WRITE

Query Parameters

newsletterIdstringRequired
Newsletter identifier.

Request Body

JSON
{
  "newsletter": {
    "contact_ids": [
      "cust_1",
      "cust_2",
      "cust_3"
    ]
  }
}
newsletter.contact_idsarrayRequired
Recipient customer IDs to store on the campaign.

Responses

Recipients synchronized.

{
  "newsletter_id": "672489...",
  "saved_recipients": 2,
  "skipped_suppressed": 1,
  "skipped_not_found": 0
}

Queue Campaign Dispatch

POST
/2026-02-01/newsletters/{newsletterId}/queue.json
Required permissionnewsletters:writeApiAccessPermission::NEWSLETTERS_WRITE

Query Parameters

newsletterIdstringRequired
Newsletter identifier.

Responses

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"
}

Send Test Email

POST
/2026-02-01/newsletters/{newsletterId}/test.json
Required permissionnewsletters:writeApiAccessPermission::NEWSLETTERS_WRITE

Query Parameters

newsletterIdstringRequired
Newsletter identifier.

Request Body

JSON
{
  "newsletter": {
    "test_email": "qa@example.com"
  }
}
newsletter.test_emailstringRequired
Target email for test send.

Responses

Test email sent.

{
  "newsletter_id": "672489...",
  "test_email": "qa@example.com",
  "sent": true
}

Get Campaign Stats

GET
/2026-02-01/newsletters/{newsletterId}/stats.json
Required permissionnewsletters:readApiAccessPermission::NEWSLETTERS_READ

Query Parameters

newsletterIdstringRequired
Newsletter identifier.
failure_limitinteger
Maximum failed/skipped items to include.

Responses

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": []
}

Suppression List

List Unsubscribes

GET
/2026-02-01/newsletters/unsubscribes.json
Required permissionnewsletter_unsubscribes:readApiAccessPermission::NEWSLETTER_UNSUBSCRIBES_READ

Query Parameters

sizeinteger
Records per page (max 100).
pageinteger
Page index.

Responses

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
    }
  ]
}

Add Unsubscribe

POST
/2026-02-01/newsletters/unsubscribes.json
Required permissionnewsletter_unsubscribes:writeApiAccessPermission::NEWSLETTER_UNSUBSCRIBES_WRITE

Query Parameters

No query parameters required.

Request Body

JSON
{
  "unsubscribe": {
    "email": "user@example.com",
    "reason": "Support request"
  }
}
unsubscribe.emailstringRequired
Email to suppress.
unsubscribe.reasonstring
Optional reason for audit trail.

Responses

Email added to suppression list.

{
  "email": "user@example.com",
  "unsubscribed": true
}

Resubscribe Email

POST
/2026-02-01/newsletters/unsubscribes/resubscribe.json
Required permissionnewsletter_unsubscribes:writeApiAccessPermission::NEWSLETTER_UNSUBSCRIBES_WRITE

Query Parameters

No query parameters required.

Request Body

JSON
{
  "unsubscribe": {
    "email": "user@example.com"
  }
}
unsubscribe.emailstringRequired
Email to remove from suppression list.

Responses

Email removed from suppression list.

{
  "email": "user@example.com",
  "resubscribed": true
}

Email not found in suppression list.

{
  "error": "Email not found in suppression list"
}

On this page