Help

Use the API.

Add subscribers, draft campaigns, send one off emails and hear about new sign ups straight away, all from your own code.

1Before you start

The API lives on your own install, at your own web address. Everything here talks to your Newsletter Sender, not to us. In the examples, swap news.yourdomain.com for your install's address.

  1. Open Settings, then API. In your Newsletter Sender, click Settings then API. Your API address is at the top, with a copy button. It looks like https://news.yourdomain.com/api/v1.
  2. Make a key. Click Make a key and give it a name you'll recognise later, such as Website sign up form. Each key belongs to one brand.
  3. Copy the key straight away. It starts ns_live_ and is shown once only. Keep it somewhere safe, such as your password manager or your server's secrets. You can have up to 10 keys for each brand, and you can delete one at any time.
Keep your key on your server. Anyone with your key can add and remove subscribers and send email as your brand. Never put it in a web page, an app or a public repository. If one ever leaks, delete it under Settings, API and make a new one.

2Keys, limits and errors

Send your key in a header with every request. Either of these works:

Authorization: Bearer ns_live_...
X-API-Key: ns_live_...

A key in the web address, such as ?key=ns_live_..., is refused. Web addresses end up in logs and browser history, so if you've ever sent a key that way, delete it and make a new one.

WhatHow it works
FormatJSON in and JSON out. Send a JSON body with POST and PATCH.
Limit60 requests a minute for each key. Over that you get 429 and a Retry-After header saying how many seconds to wait.
Where fromCall it from your server or a script. Browsers are not allowed to call it directly, which keeps your key out of web pages.
ErrorsEvery error is a plain English message and a link back to this page, such as { "error": "Please check the email address.", "docs": "https://newslettersender.com/help/api/" }

To check your key works, ask which brand it belongs to:

curl https://news.yourdomain.com/api/v1/me \
  -H "Authorization: Bearer ns_live_..."
{ "brand": { "id": "...", "name": "Weekly Notes" }, "docs": "https://newslettersender.com/help/api/" }

3Add a subscriber

This is the one most people need: someone fills in a form on your website and you add them to a list.

curl https://news.yourdomain.com/api/v1/subscribers \
  -H "Authorization: Bearer ns_live_..." \
  -H "Content-Type: application/json" \
  -d '{"email":"sam@example.com","name":"Sam","list":"weekly","tags":["spring-offer"]}'
{
  "id": "3f9c2a7b81d04e6a",
  "email": "sam@example.com",
  "name": "Sam",
  "fields": {},
  "tags": ["spring-offer"],
  "status": "pending_confirmation",
  "lists": [
    { "id": "...", "slug": "weekly", "name": "Weekly", "status": "pending_confirmation",
      "joined_at": "2026-11-06T09:30:00.000Z", "confirmed_at": null, "left_at": null }
  ],
  "created_at": "2026-11-06T09:30:00.000Z",
  "result": "pending_confirmation"
}
FieldWhat to send
emailTheir email address. Required.
list or listsThe list's slug or id, such as "weekly", or several as ["weekly", "offers"]. Required.
nameTheir name. Optional.
fieldsYour own extra details, such as { "city": "Leeds" }. Optional.
tagsTags to add, such as ["spring-offer"]. Optional.
confirmedLeave this out and they get your confirmation email first, which is the safe choice. Send true only for people who have already said yes to your emails somewhere else.

The result tells you what happened:

ResultWhat it means
pending_confirmationNew, and we've sent them the confirmation email. You get 201.
activeNew and subscribed straight away, because you sent "confirmed": true. You get 201.
alreadyThey were already on that list, so nothing changed. You get 200.
elsewhereThey're on another of your lists and this brand allows one list each, so they weren't added.

4Find, change and remove subscribers

Wherever you see {id or email}, you can use the subscriber's id or their email address.

RequestWhat it does
GET /api/v1/subscribersA page of subscribers, 50 at a time. Add ?list=weekly, &status=active (or pending_confirmation or unsubscribed) and &limit=100 to narrow it down. When there are more, the reply has a next_cursor: send it back as &cursor=... for the next page.
GET /api/v1/subscribers/{id or email}One subscriber, with their fields, tags and every list they've joined or left.
PATCH /api/v1/subscribers/{id or email}Change their name, fields or tags. In fields, set a field to null to remove it. tags replaces all their tags; add_tags and remove_tags change just the ones you name.
POST /api/v1/subscribers/{id or email}/unsubscribeUnsubscribes them from every list, or just one if you send { "list": "weekly" }.
DELETE /api/v1/subscribers/{id or email}Removes everything held about them, for when someone asks to be forgotten. This can't be undone.
curl -X PATCH https://news.yourdomain.com/api/v1/subscribers/sam@example.com \
  -H "Authorization: Bearer ns_live_..." \
  -H "Content-Type: application/json" \
  -d '{"fields":{"city":"Leeds"},"add_tags":["customer"]}'

5Lists

GET /api/v1/lists gives you every list in the brand, with its id, slug, name, description, whether it uses double opt in and how many active subscribers it has. Use the slug or id wherever a request asks for a list.

6Campaigns

The API can draft a campaign, but it never sends one. You check it and press send in the app, so nothing goes out to your whole list by mistake.

curl https://news.yourdomain.com/api/v1/campaigns \
  -H "Authorization: Bearer ns_live_..." \
  -H "Content-Type: application/json" \
  -d '{"subject":"Spring news","list":"weekly","html":"<p>Hello</p>"}'
{ "id": "...", "status": "draft", "edit_url": "https://news.yourdomain.com/#email/...", "audience": 1240 }
RequestWhat it does
POST /api/v1/campaignsMakes a draft. Send subject and list or lists, and optionally name, preheader, html and tags. The edit_url opens it in the app and audience is how many people it would go to.
GET /api/v1/campaignsYour last 100 campaigns, with their status and how many have been sent.
GET /api/v1/campaigns/{id}One campaign with its numbers: sent, failed, queued, unsubscribed, bounced and complained.

7Send a one off email

For a single email to one person, such as a receipt or a password reset. It goes from your brand's From name and address, through the same sending service as your newsletters.

curl https://news.yourdomain.com/api/v1/emails \
  -H "Authorization: Bearer ns_live_..." \
  -H "Content-Type: application/json" \
  -d '{"to":"sam@example.com","subject":"Your receipt","text":"Thanks for your order."}'
{ "sent": true, "message_id": "..." }

Send to, subject and html or text (or both), and optionally reply_to. You get 202 once it's handed to your sending service. Addresses that have bounced or complained are refused, to protect your sending reputation.

8Webhooks

A webhook tells your own system the moment something happens, such as a new subscriber, so you don't have to keep asking.

  1. Add a webhook. In Settings, API, Webhooks, enter your https address and tick the events you want. You can have up to 5 for each brand.
  2. Copy the signing secret. It starts whsec_ and is shown once only. You use it to check each message really came from your Newsletter Sender.
  3. Send a test. Click Send a test. Your address gets an event called test.
EventWhen it's sent
subscriber.subscribedSomeone is now subscribed (they confirmed, or were added as confirmed).
subscriber.pendingSomeone signed up and has been sent the confirmation email.
subscriber.unsubscribedSomeone unsubscribed.
subscriber.bouncedEmail to someone bounced, so they won't be sent any more.
subscriber.complainedSomeone marked your email as spam, so they won't be sent any more.
campaign.startedA campaign started sending.
campaign.sentA campaign finished sending.
campaign.pausedA campaign was paused.

Each one arrives as a POST with a JSON body like this:

{
  "id": "...",
  "event": "subscriber.subscribed",
  "created_at": "2026-11-06T09:31:00.000Z",
  "data": {
    "subscriber": { "id": "3f9c2a7b81d04e6a", "email": "sam@example.com", "name": "Sam" },
    "campaign_id": null,
    "detail": "Weekly",
    "at": "2026-11-06T09:31:00.000Z"
  }
}

Campaign events have campaign_id, detail and at in data. Three headers come with it: NewsletterSender-Event (the event), NewsletterSender-Delivery (the id) and NewsletterSender-Signature.

9Check the signature

The signature header looks like t=1762421460,v1=5d41.... t is when it was sent, in seconds, and v1 is an HMAC SHA256 of the time, a full stop and the raw body, made with your signing secret. Check it before you trust the message, and turn away anything more than 5 minutes old. In Node:

import crypto from "node:crypto";

// raw is the body exactly as it arrived, before you parse the JSON.
function verify(raw, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const now = Date.now() / 1000;
  if (!t || !v1 || Number(t) + 300 < now || Number(t) > now + 300) return false;
  const want = crypto.createHmac("sha256", secret).update(`${t}.${raw}`).digest("hex");
  return want.length === v1.length && crypto.timingSafeEqual(Buffer.from(want), Buffer.from(v1));
}
DeliveryHow it works
WhenWithin about a minute of the event.
SuccessAnswer with any 2xx status, such as 200.
RetriesEach event is sent once. If your address is down, that event is missed, so you can always fetch the subscriber to catch up.
Switched offAfter 10 failures in a row the webhook is switched off. Fix your address, then press Turn on under Settings, API, Webhooks.

10If something goes wrong

Every error comes with a plain English error message saying what to fix. These are the status codes you might see:

StatusWhat it means
400Something in the request needs fixing, such as a missing list, a bad email address, JSON that won't read or a key sent in the web address.
401No key, or the key isn't right. Check you copied all of it, and that it hasn't been deleted.
404That list, subscriber, campaign or address doesn't exist. Check the spelling of the slug or id.
405The right address but the wrong method, such as GET where it needs POST.
409We can't do that to this address: it's on your blocklist, it bounced or complained, or sending isn't set up yet.
429Over 60 requests a minute. Wait for the seconds in Retry-After, then try again.
502Your sending service turned the email down. Check it's set up under Settings, and try again.

Still stuck? Get in touch. Please never send your keys.