Docs/Sending email

Tags, topics & campaigns

tags, topic, and campaign are labels you attach to a message. They never change what is sent. Their job is to travel with the message onto every event it produces, so you can filter and report on the event stream. Two of them also shape delivery: topic is the unsubscribe and suppression boundary, and each campaign gets its own delivery queues.

What they have in common

All three are optional fields on POST /v1/email. A value you set is copied onto every event for that message: not just email.sent, but the delivery, bounce, open, click, and complaint events that follow.

curl https://api.anypost.com/v1/email \
  -H "Authorization: Bearer $ANYPOST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Acme <[email protected]>",
    "to": ["[email protected]"],
    "subject": "March newsletter",
    "html": "<p>This month at Acme.</p>",
    "topic": "newsletter",
    "campaign": "march-2026",
    "tags": ["newsletter", "us-region"]
  }'

None of the three is a per-message identifier. To correlate one specific message, use the email_ ID returned from the send; see Send a single email. Putting a unique value such as an order ID into a tag or a campaign defeats their purpose, and for campaign will hit a rate limit.

Tags

tags is an array of grouping labels. Each tag is 1 to 64 characters of A-Za-z0-9_-, and a message may carry up to 10. Duplicates are removed server-side, keeping first-occurrence order.

Use tags for the dimensions you want to slice events by: a cohort, a region, a mailing list, transactional versus marketing. A message can carry several at once, which is what makes them a grouping tool rather than a single category.

Topic

topic is a single label, 1 to 64 characters of lowercase a-z0-9_.-. It names the kind of mail this message is: newsletter, receipts, product-updates.

Topic is also the unsubscribe and suppression boundary. A recipient who unsubscribes does so from one topic, and a suppression applies within the topic it was recorded under. A message sent with no topic is treated as transactional: it skips topic-scoped suppression entirely, which is what keeps a password reset or a one-time code deliverable. Topic is required when a message generates an unsubscribe link. See Unsubscribe handling and Suppressions.

Campaign

campaign is a single label, 1 to 64 characters of A-Za-z0-9_-. It marks a message as part of one send effort: march-2026, black-friday, onboarding-drip.

Campaign is coarse by design. A team may use at most 200 distinct campaign values per rolling hour, and at most 1,000 per rolling 72 hours, the lifetime of a campaign's delivery queues. A single send that would cross either cap is rejected with 429; the response's limit field tells you which window tripped. In a batch the denial is per entry, as described below. Those ceilings are generous for real stream segmentation and deliberately too low to use campaign as a per-message identifier. If you hit one, you are almost certainly using a unique value per send where a tag or the email_ ID belongs.

Distinct-value limits

Each label caps how many distinct values a team can use, so none of them can stand in for a per-message identifier:

LabelDistinct values allowedscope on the 429
tags1,000 per rolling hourtag_cardinality
topic200 per rolling hourtopic_cardinality
campaign200 per rolling hour, and 1,000 per rolling 72 hourscampaign_cardinality

Reusing a value already seen in the window never counts against the cap. A send that would add a value past it is rejected with 429 quota_exceeded; in a batch only that entry fails.

Choosing between them

FieldHow manyUse it for
tagsUp to 10 per messageSlicing events by cohort, region, list, or message type.
topicOne per messageThe kind of mail, and the unsubscribe and suppression boundary.
campaignOne per messageGrouping a single send effort for reporting.

A send can set all three, none, or any combination. A fourth label, the template_id of a templated send, filters events the same way.

In a batch

In a batch send, topic and campaign set in defaults are each overridden by an entry that supplies its own. tags are concatenated, batch defaults first, then de-duplicated, so a default tag applies to every entry on top of its own.

The caps above apply per entry, not per request. An entry carrying an over-cap value fails on its own with a quota_exceeded error naming the scope and retry_after_seconds, and the rest of the batch queues and delivers. Only a batch where every entry is denied comes back as a 429.

Where to go next