eusend
Audiences

Topics

Named categories of email your contacts can subscribe to and leave independently, so an unsubscribe becomes a preference change instead of losing the contact entirely.

A topic is a kind of email — "Monthly newsletter", "Product updates", "Beta programme" — that a contact can join or leave on its own. Scope a broadcast to a topic and it reaches only the people who want that kind of mail.

Without topics, a recipient who likes the monthly newsletter but not the weekly product blast has exactly one lever: leave. The lever they reach for when leaving feels too blunt is the spam button, and that costs every sender on the shared IPs — which is the real reason this feature exists.

Topics belong to the organization, not to one audience, so "Product updates" means the same thing wherever the contact sits.

Opt-in or opt-out

Every topic has a default that decides what a contact who has never expressed a preference receives:

DefaultA contact who has never chosen
opt_inreceives it, until they explicitly unsubscribe
opt_outdoes not receive it, until they explicitly join

Use opt_in for mail the list signed up for — your newsletter is the obvious case. Use opt_out for anything a contact has to ask for: a beta programme, a high-frequency digest, a regional announcement.

The default cannot be changed after creation. Flipping an opt_out topic to opt_in would start mailing every contact who never asked for it, and nothing stored anywhere says which of them would have agreed. It is a consent decision, so changing it means creating a new topic.

Because a new opt_in topic reaches everyone immediately and a new opt_out topic reaches nobody, neither needs a backfill — only explicit choices are stored.

The global subscription still wins

A contact whose subscription is off receives nothing, whatever their topic preferences say. Topics narrow who gets a broadcast; they never widen it. Unsubscribing from every topic is also not the same as unsubscribing from the list — see Unsubscribes.

Visibility

private (the default) or public, controlling whether the topic is listed on the hosted unsubscribe page to a contact who is not already opted in.

Keep a topic private when its name is an internal one. Advertising "Enterprise outreach" to someone who is not on it tells them something you probably did not mean to, and the natural response to an unfamiliar name on an unsubscribe page is to untick it. A public topic is one you are happy for anyone to discover and join.

A private topic is always shown to a contact who explicitly opted in — otherwise they would have no way to leave it.

Sending to a topic

Pass topic_id when creating a broadcast:

only the people who want product updates
curl -X POST https://api.eusend.dev/broadcasts \
  -H "Authorization: Bearer eu_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "October product update",
    "audience_id": "550e8400-e29b-41d4-a716-446655440000",
    "topic_id": "b6d24b8e-af0b-4c3c-be0c-359bbd97381e",
    "from": "hello@acme.com",
    "subject": "What shipped in October",
    "html": "<p>Hi {{first_name}}…</p>"
  }'

The recipient count shown on the broadcast reflects the topic, so it matches what is actually sent. PATCH topic_id to null on a draft to widen it back to the whole audience.

A broadcast sent under a topic also carries a topic-scoped unsubscribe link: the footer link and the List-Unsubscribe header both retire that topic rather than the whole list, so one-click unsubscribe in Gmail becomes a preference change.

Topics scope broadcasts only. POST /emails is untouched — a receipt or a password reset must never be filtered by a marketing preference.

POST/topics
ParameterTypeDescription
namerequiredstringWhat the recipient sees on the unsubscribe page if the topic is visible to them. At most 50 characters, unique within the organization.
default_subscriptionrequiredstring"opt_in" or "opt_out". Cannot be changed later.
descriptionstring | nullShown under the name on the unsubscribe page. At most 200 characters. Worth writing — it is what tells a recipient whether to keep the box ticked.
visibilitystring"private" (default) or "public".
response — 201 Created
{
  "id": "b6d24b8e-af0b-4c3c-be0c-359bbd97381e",
  "name": "Product updates",
  "description": "New features and changes.",
  "defaultSubscription": "opt_in",
  "visibility": "public",
  "subscriberCount": 0,
  "createdAt": "2026-09-22T10:14:52.110Z",
  "updatedAt": "2026-09-22T10:14:52.110Z"
}

Returns 409 CONFLICT if a topic with that name already exists, and 403 PLAN_LIMIT_EXCEEDED past 100 topics.

GET/topics

Every topic in the organization, ordered by name. Unpaginated — the list is capped at 100 and both the composer's picker and the unsubscribe page want all of it.

subscriberCount is the number of contacts who have explicitly opted in. For an opt_out topic that is the whole reachable audience; for an opt_in topic it is not, since everyone who never chose is also subscribed.

GET/topics/:id

One topic by id.

PATCH/topics/:id
ParameterTypeDescription
namestringAt most 50 characters.
descriptionstring | nullSend null to clear it.
visibilitystring"public" or "private".

default_subscription is rejected here — see the warning above.

DELETE/topics/:id
response
{ "id": "b6d24b8e-af0b-4c3c-be0c-359bbd97381e", "deleted": true }

Every stored preference for the topic goes with it. Any draft or scheduled broadcast scoped to it widens back to its whole audience, so check before deleting a topic a scheduled send is pointing at. Broadcasts already sent keep their recipient records.

Setting a contact's preference

PUT/topics/:id/contacts/:contactId
subscribe a contact
curl -X PUT https://api.eusend.dev/topics/b6d24b8e/contacts/9f1c2d3e \
  -H "Authorization: Bearer eu_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "subscribed": true }'
DELETE/topics/:id/contacts/:contactId

Clears the override so the contact falls back to the topic's default. This is different from { "subscribed": false }: it forgets that they ever chose, so on an opt_in topic they start receiving it again.

On the unsubscribe page

A recipient who clicks unsubscribe gets a checkbox per visible topic and a Save preferences button, alongside the usual unsubscribe-from-everything button. No JavaScript, no login — it is the same hosted page, branded the same way.

Organizations with no topics see exactly the page they saw before.

SDKs

Node.js
const { data: topic } = await eusend.topics.create({
  name: 'Product updates',
  defaultSubscription: 'opt_in',
  visibility: 'public',
})
await eusend.topics.subscribe(topic.id, contactId, true)
Python
topic = eusend.Topics.create({"name": "Product updates", "default_subscription": "opt_in"})
eusend.Topics.subscribe(topic["id"], contact_id, True)
Go
topic, _ := client.Topics.Create(&eusend.CreateTopicRequest{
	Name:                "Product updates",
	DefaultSubscription: "opt_in",
})
client.Topics.Subscribe(topic.Id, contactId, true)