Search Documentation

Search for a documentation page...

Newsletters API

REST API endpoints for creating, sending, and managing newsletter campaigns.

Newsletters are email campaigns broadcast to your subscribers. A newsletter starts as a draft, can be edited freely, and is sent in the background once you call the send endpoint. Its status moves through draft → sending → sent, or failed if delivery breaks down. Newsletters can also be scheduled for a future send time. Failed newsletters can be reset to draft and retried. Manage the subscriber list itself via the Subscribers API.

List Newsletters

GET /api/v1/newsletters

Returns newsletter campaigns, newest first.

Query Parameters

ParameterTypeDefaultDescription
limitnumber50Newsletters per page (1-100)
offsetnumber0Newsletters to skip
statusstring–Filter by status: draft, scheduled, sending, sent, or failed
curl \
-H "Authorization: Bearer your_api_key" \
"https://your-store.yns.store/api/v1/newsletters?status=sent"

Response (200)

{
"data": [
{
"id": "0191abc0-1234-7def-8000-000000000001",
"subject": "Spring Sale – 20% off everything",
"content": "Our spring sale is live. Use code SPRING20 at checkout.",
"contentFormat": "markdown",
"status": "sent",
"audience": "all_active",
"groupFilter": null,
"products": [],
"scheduledAt": null,
"recipientCount": 1240,
"sentAt": "2024-06-15T12:00:00.000Z",
"failedAt": null,
"errorMessage": null,
"createdAt": "2024-06-14T09:00:00.000Z",
"updatedAt": "2024-06-15T12:00:00.000Z"
}
],
"meta": {
"count": 1,
"offset": 0,
"limit": 50
}
}

Create Newsletter

POST /api/v1/newsletters

Creates a draft campaign. Use the send endpoint to broadcast it once you're ready.

Request Body

FieldTypeRequiredDescription
subjectstringYesEmail subject line
contentstringYesEmail body: plain text with the editor's light formatting, or an MJML template when contentFormat is mjml
contentFormatstringNomarkdown (default) or mjml. See MJML templates
audiencestringNoPreset recipient audience, resolved live at send time. One of: all_active (default), customers, repeat_customers, never_purchased, recent_signups
groupFilterobject | nullNoNarrows the audience by subscriber group. See Group filter
productsarrayNoFeatured product cards rendered in the email, in display order (max 6). Each entry is an object with productId (required, UUID) and optional variantId (UUID – defaults to cheapest variant). Prices are resolved at send time.
curl -X POST \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{
"subject": "Spring Sale – 20% off everything",
"content": "Our spring sale is live. Use code SPRING20 at checkout.",
"audience": "customers",
"groupFilter": {
"include": ["0191abc0-1234-7def-8000-0000000000a1"],
"exclude": ["0191abc0-1234-7def-8000-0000000000a2"],
"match": "any"
},
"products": [
{ "productId": "0191abc0-1234-7def-8000-000000000010" },
{ "productId": "0191abc0-1234-7def-8000-000000000011", "variantId": "0191abc0-1234-7def-8000-000000000020" }
]
}' \
https://your-store.yns.store/api/v1/newsletters

Response (201)

{
"id": "0191abc0-1234-7def-8000-000000000001",
"subject": "Spring Sale – 20% off everything",
"content": "Our spring sale is live. Use code SPRING20 at checkout.",
"contentFormat": "markdown",
"status": "draft",
"audience": "customers",
"groupFilter": {
"include": ["0191abc0-1234-7def-8000-0000000000a1"],
"exclude": ["0191abc0-1234-7def-8000-0000000000a2"],
"match": "any"
},
"products": [
{ "productId": "0191abc0-1234-7def-8000-000000000010" },
{ "productId": "0191abc0-1234-7def-8000-000000000011", "variantId": "0191abc0-1234-7def-8000-000000000020" }
],
"scheduledAt": null,
"recipientCount": 0,
"sentAt": null,
"failedAt": null,
"errorMessage": null,
"createdAt": "2024-06-14T09:00:00.000Z",
"updatedAt": "2024-06-14T09:00:00.000Z"
}

Group filter

groupFilter sends to some subscriber groups, leaves others out, or both. It always narrows the audience preset, never widens it.

FieldTypeDefaultDescription
includestring[][]Group ids (max 20). When not empty, only members of these groups receive the newsletter
matchstringanyany: a member of at least one include group. all: a member of every include group
excludestring[][]Group ids (max 20). Members of any of these groups never receive the newsletter

The recipients are: the audience preset, and (when include is not empty) in any/all of the include groups, and not in any exclude group. Unsubscribed, unconfirmed and paused subscribers are never sent to, whatever their groups. A group cannot be in both lists (400). null, or both lists empty, means no group filter and is returned as null.

Group ids are resolved when the newsletter is sent. If a group is deleted after you saved the filter, its id stays: in include it matches nobody, in exclude it excludes nobody, so a deleted group never makes a send larger. If that leaves nobody to send to, the send is rejected with 400 like any empty audience.

MJML templates

With "contentFormat": "mjml", content is a complete MJML template (up to 512 KB) and becomes the whole email: the store's branded layout, products cards and standard footer are not added. The template is compiled when you save it and again at send time, and it must compile without errors. Otherwise create, update and send answer 400 with the compiler's errors:

{
"error": "The MJML template has errors",
"details": [{ "line": 12, "message": "mj-text cannot be used inside mj-section, only inside: mj-attributes, mj-column, mj-hero" }],
"hint": "Fix the template and send it again."
}

These merge tags are filled in for each recipient, HTML-escaped. Write {{tag|fallback}} to use a fallback when the value is empty, e.g. {{subscriber_name|there}}. Other {{tags}} are sent as written.

TagValue
{{subscriber_name}}The subscriber's name, or empty
{{subscriber_email}}The subscriber's email address
{{store_name}}Store name
{{store_url}}Storefront URL
{{store_address}}The store's postal address on one line
{{unsubscribe_url}}The recipient's unsubscribe link

A template without {{unsubscribe_url}} gets a footer section with the store address and an unsubscribe link at the end of <mj-body>. mj-include is ignored, so images and styles must be inline or use absolute https:// URLs.


Get Newsletter

GET /api/v1/newsletters/{id}

Returns a single newsletter by UUID, including status, recipientCount, and the sent/failed timestamps.

Response (200)

{
"id": "0191abc0-1234-7def-8000-000000000001",
"subject": "Spring Sale – 20% off everything",
"content": "Our spring sale is live. Use code SPRING20 at checkout.",
"contentFormat": "markdown",
"status": "draft",
"audience": "all_active",
"groupFilter": null,
"products": [],
"scheduledAt": null,
"recipientCount": 0,
"sentAt": null,
"failedAt": null,
"errorMessage": null,
"createdAt": "2024-06-14T09:00:00.000Z",
"updatedAt": "2024-06-14T09:00:00.000Z"
}

Not Found (404)

{
"error": "Newsletter not found"
}

Update Newsletter

PATCH /api/v1/newsletters/{id}

Edits the subject, content, content format and/or group filter of a draft newsletter. Only drafts can be edited – editing a newsletter in any other status returns 409.

Request Body

FieldTypeRequiredDescription
subjectstringNoEmail subject line
contentstringNoEmail body: plain text with light formatting, or an MJML template when the content format is mjml
contentFormatstringNomarkdown or mjml. An MJML body must compile, or the update answers 400 with the errors (see MJML templates)
groupFilterobject | nullNoReplaces the group filter. null clears it; omit it to keep the current one
curl -X PATCH \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{"subject": "Spring Sale – 25% off this weekend"}' \
https://your-store.yns.store/api/v1/newsletters/0191abc0-1234-7def-8000-000000000001

Response (200)

Returns the updated newsletter.

Not a Draft (409)

{
"error": "Only draft newsletters can be edited"
}

Delete Newsletter

DELETE /api/v1/newsletters/{id}

Deletes a draft newsletter by UUID. Only drafts can be deleted – deleting a newsletter in any other status returns 409.

curl -X DELETE \
-H "Authorization: Bearer your_api_key" \
https://your-store.yns.store/api/v1/newsletters/0191abc0-1234-7def-8000-000000000001

Response (200)

{
"ok": true,
"deleted": 1
}

Not a Draft (409)

{
"error": "Only draft newsletters can be deleted"
}

Send Newsletter

POST /api/v1/newsletters/{id}/send

Broadcasts the draft in the background to its recipients: the audience preset, narrowed by the group filter when there is one. Before dispatching, YNS validates several preconditions: a configured store address, at least one recipient, and remaining capacity in your monthly email quota. A sending domain is not required: until the store's own domain is verified, campaigns go out from a shared address under the store's name. If any check fails the send is rejected with the appropriate status (400, 403, 404, or 409) and no emails go out.

curl -X POST \
-H "Authorization: Bearer your_api_key" \
https://your-store.yns.store/api/v1/newsletters/0191abc0-1234-7def-8000-000000000001/send

Response (202)

{
"success": true,
"recipientCount": 1240,
"message": "Newsletter send started; emails are sending in the background."
}

Errors

StatusMeaning
400Precondition not met (store address, no recipients for this audience and group filter, or an MJML template that no longer compiles)
403Monthly email quota would be exceeded
404Newsletter not found
409Newsletter is not a draft

Retry Newsletter

POST /api/v1/newsletters/{id}/retry

Resets a failed newsletter back to draft so it can be edited and re-sent. Returns 409 if the newsletter is not in the failed status.

curl -X POST \
-H "Authorization: Bearer your_api_key" \
https://your-store.yns.store/api/v1/newsletters/0191abc0-1234-7def-8000-000000000001/retry

Response (200)

Returns the newsletter reset to draft status.

{
"id": "0191abc0-1234-7def-8000-000000000001",
"subject": "Spring Sale – 20% off everything",
"content": "Our spring sale is live. Use code SPRING20 at checkout.",
"contentFormat": "markdown",
"status": "draft",
"audience": "all_active",
"groupFilter": null,
"products": [],
"scheduledAt": null,
"recipientCount": 0,
"sentAt": null,
"failedAt": null,
"errorMessage": null,
"createdAt": "2024-06-14T09:00:00.000Z",
"updatedAt": "2024-06-15T13:00:00.000Z"
}

Not Failed (409)

{
"error": "Only failed newsletters can be retried"
}