PolderAPI

Backlog API

Notify

Read contacts and segments, write mailing drafts, send test mails, and send or schedule a mailing to customers.

Base URLhttps://backlog.polder.work

Notify sends mailings to the contacts of your customers. The API does what the screens do, through the same actions and the same rules.

Scopes and who may do what

Three scopes, each a bigger step than the last:

Scope What it allows
notify:read Read categories, contacts, segments, audiences and mailings (with their results). Contacts are other people's addresses, so this is its own scope.
notify:write Create and change drafts, send test mails. Nothing reaches a customer.
notify:send Send and schedule a mailing. This cannot be taken back, so no token has it unless somebody ticked it.

A scope only narrows what the token's owner may do. On top of it:

  • A guest (a client) gets 403 everywhere, and so does a workspace that has switched Notify off.
  • A member may read, draft and send test mails to themselves or a colleague.
  • Only a workspace administrator may send or schedule, and send a test mail to any address.

The MCP server offers the read and draft tools (polder_list_mailings, polder_create_mailing_draft, …) but never sending: an assistant prepares a draft and a person sends it. See Scopes.

A mailing

A mailing is a draft until somebody sends it.

Field Description
id Mailing id.
subject, preheader Subject line and preview text.
status draft, scheduled, sending, paused, sent, cancelled or failed.
category_id The mail category (mail_category_id when you write it).
include_segment_ids, exclude_segment_ids Segments whose contacts receive it, and segments whose contacts do not.
track Whether opens and clicks are measured.
reply_team_id The team that receives replies.
recipients_total Number of recipients, set at the moment sending starts.
scheduled_at, started_at, finished_at, created_at, updated_at ISO 8601 or null.
body The HTML body. Only returned when you read or write a single mailing.

List categories

GET/api/v1/workspaces/{workspace}/notify/categoriesscope notify:read

The workspace's mail categories. A mailing needs one; an unsubscribable category is one that recipients can opt out of.

curl https://backlog.polder.work/api/v1/workspaces/7/notify/categories \
  -H "Authorization: Bearer $POLDER_TOKEN"
{ "data": [{ "id": 2, "name": "Product news", "unsubscribable": true }] }

List contacts

GET/api/v1/workspaces/{workspace}/notify/contactsscope notify:read

The contacts of the workspace's customers, ordered by name and paged.

Parameter Description
q Search in the contact's name, email address and customer name.
page, per_page Paging. per_page defaults to 50, at most 200.
{
  "data": [{
    "id": 31, "name": "Sanne de Vries", "email": "sanne@example.com", "language": "nl", "role": "Owner",
    "status": "…", "customer": { "id": 4, "name": "Acme" }
  }],
  "meta": { "page": 1, "per_page": 50, "total": 1, "last_page": 1 }
}

List segments

GET/api/v1/workspaces/{workspace}/notify/segmentsscope notify:read

All segments of the workspace, ordered by name. kind and filters describe how the segment picks its customers; a segment that lists customers or contacts by hand also has customer_ids or contact_ids.

{ "data": [{ "id": 5, "name": "Dutch customers", "kind": "…", "filters": {} }] }

Preview a segment's audience

GET/api/v1/workspaces/{workspace}/notify/segments/{segment}/audiencescope notify:read

Who one segment reaches for a category right now: the counts and why people fall away, plus a sample. The addresses of everybody are never listed.

Parameter Required Description
category_id yes A category of this workspace. Unsubscribes depend on the category.

A segment of another workspace is a 404.

{
  "data": {
    "customers": 12, "contacts": 40, "reachableContacts": 38, "reachableCustomers": 12,
    "dropped": { "unsubscribed": 1, "suppressed": 1, "expired": 0, "customersWithoutAddress": 0 },
    "sample": [{ "id": 31, "name": "Sanne de Vries", "email": "sanne@example.com", "customer": "Acme", "blocked": null }]
  }
}

sample holds at most 50 contacts; blocked says why a contact is dropped, or is null.

List mailings

GET/api/v1/workspaces/{workspace}/notify/mailingsscope notify:read

Mailings, newest first, paged. Filter with ?status= (one of the statuses above), page with page and per_page. The body is left out of the list.

{
  "data": [{ "id": 12, "subject": "Maintenance on Friday", "status": "draft", "…": "…" }],
  "meta": { "page": 1, "per_page": 50, "total": 1, "last_page": 1 }
}

Get a mailing

GET/api/v1/workspaces/{workspace}/notify/mailings/{mailing}scope notify:read

One mailing with its body and stats. A mailing from another workspace is a 404.

stats counts recipients: sent, pending, failed, cancelled, delivered, bouncedHard, bouncedSoft, complaints, unsubscribed, replies, and for tracking opened, clicked, clicks and botEvents.

Note opened is an upper bound. Mail clients that preload images and link scanners show up as opens; those are counted separately as botEvents where Polder recognises them.

{
  "data": {
    "id": 12, "subject": "Maintenance on Friday", "status": "sent", "recipients_total": 38,
    "body": "<p>Hello {{naam}},</p>…",
    "stats": { "sent": 38, "delivered": 37, "bouncedHard": 1, "opened": 21, "clicked": 4, "…": "…" }
  }
}

Create a draft

POST/api/v1/workspaces/{workspace}/notify/mailingsscope notify:write

Creates a mailing in status draft. Every field is optional, so you can start empty and fill it in later. Sending checks that subject, text, category and at least one included segment are present. The mailing starts with the workspace's default for track, and replies go to the team of the workspace's catch-all mailbox until you set reply_team_id. Answers 201 with the mailing, body included.

Body

Field Type Required Description
subject string no At most 200 characters.
preheader string no At most 200 characters.
body string no Simple HTML, at most 300,000 characters. {{naam}} and {{klant}} are filled in per recipient.
mail_category_id integer no A category of this workspace.
include_segment_ids integer[] no Segments of this workspace.
exclude_segment_ids integer[] no Segments of this workspace.
track boolean no Measure opens and clicks.
reply_team_id integer no A team you may see.
curl https://backlog.polder.work/api/v1/workspaces/7/notify/mailings \
  -H "Authorization: Bearer $POLDER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"subject": "Maintenance on Friday", "body": "<p>Hello {{naam}},</p>", "mail_category_id": 2, "include_segment_ids": [5]}'

Update a draft

PATCH/api/v1/workspaces/{workspace}/notify/mailings/{mailing}scope notify:write

Changes the fields you send, with the same fields and rules as creating. Only a draft or a scheduled mailing can change; anything else answers 409.

Really changing a scheduled mailing takes it back to draft, so what goes out is always what somebody confirmed. The response then has unscheduled: true. Saving the same values again keeps the schedule.

{ "data": { "id": 12, "status": "draft", "…": "…", "unscheduled": true } }

Send a test mail

POST/api/v1/workspaces/{workspace}/notify/mailings/{mailing}/testscope notify:writethrottle 10 per minute

Sends the mailing to one address, exactly as recipients get it, with [Test] in front of the subject. It never touches the recipient list. The address must be yourself or a member of the workspace; an administrator may use any address.

Besides the route's limit, each person gets at most 5 test mails a minute (NOTIFY_TESTS_PER_MINUTE).

Field Type Required Description
to string yes Email address.
{ "data": { "ok": true, "error": null } }

If the mail provider refuses the message the call still answers 200 with ok: false and the provider's error.

Status reason Meaning
403 forbidden No right to use Notify here.
422 invalid_address to is not an email address.
422 address_not_allowed Not you or a member, and you are not an administrator.
422 no_connection The workspace has no sending connection.
429 rate_limited Too many test mails; context.seconds says how long to wait.

Send a mailing

POST/api/v1/workspaces/{workspace}/notify/mailings/{mailing}/sendscope notify:sendthrottle 6 per minute

Sends a draft (or a scheduled mailing) now. Polder checks everything again at this moment, freezes the audience, writes a line in the contact log of every customer reached, and hands the mailing to the queue. The answer is 202 with the mailing in status sending; follow its progress with Get a mailing.

Confirm the recipient count

Sending cannot be undone, so you must say how many people you expect to mail, the way the screen shows the number.

Field Type Required Description
count integer yes The number of reachable contacts you were shown.
typed integer from the threshold From 50 recipients on (notify.confirm_threshold) the number must be given a second time, as the screen asks somebody to type it over.

If the audience is not count any more (somebody unsubscribed, a segment changed) nothing is sent and the response tells you the real number. You can use that to find out the count: send 0 and read context.count from the audience_changed answer. For several segments or an exclusion list that is the only way to see the number for the whole mailing; the segment audience endpoint counts one segment at a time.

curl https://backlog.polder.work/api/v1/workspaces/7/notify/mailings/12/send \
  -H "Authorization: Bearer $POLDER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"count": 118, "typed": 118}'
{ "message": "audience_changed", "reason": "audience_changed", "context": { "count": 117 } }

Errors

Refusals have the shape above, with a stable reason and context holding what you need to try again.

Status reason Meaning
403 forbidden Not an administrator.
409 not_draft Already sent or going out.
422 incomplete Subject, text, category or included segments missing; context.missing lists them (subject, body, category, segments).
422 no_connection The workspace has no sending connection.
422 no_recipients Nobody left who may be mailed.
422 audience_changed The audience is not count; context.count is the current number.
422 confirm_required From the threshold on, typed has to equal the audience.

Validation errors on count and typed are the usual 422 with errors.

Schedule a mailing

POST/api/v1/workspaces/{workspace}/notify/mailings/{mailing}/schedulescope notify:sendthrottle 6 per minute

Sets a draft (or an already scheduled mailing) to go out at at. It asks for the same confirmation as sending, with count and typed, plus:

Field Type Required Description
at string yes An ISO 8601 moment with its offset, e.g. 2026-11-02T09:00:00+01:00. At least a minute ahead and at most a year.

Answers 200 with the mailing in status scheduled. The count you confirmed is an indication: the audience is worked out again at the moment of sending, as the person who scheduled it, and it is not sent if they may no longer send. Changing a scheduled mailing takes it back to draft. The errors are those of sending, plus 422 invalid_time when at is too soon or too far ahead.

curl https://backlog.polder.work/api/v1/workspaces/7/notify/mailings/12/schedule \
  -H "Authorization: Bearer $POLDER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"count": 38, "at": "2026-11-02T09:00:00+01:00"}'