Newsletter API: how to send a broadcast from your code, from consent to the report
Published Updated 10 min readBy the Rasket team

What a newsletter API is
A newsletter API is an HTTP interface for sending one message to many people who asked for it: the audience, the consent, the message and the report are all objects you create and read from code rather than from a campaign editor. The same thing is sometimes called a broadcast API, because the send itself is called a broadcast.
What separates it from the transactional send you already have is not the transport. Each recipient of a broadcast is one ordinary email through the same pipeline, with its own delivery events. The difference is what has to be true before the send is allowed: the person opted in, they can opt out in one action, and the message says who you are and where you are. A newsletter API is a transactional API with those rules built in, and the transactional-versus-marketing post explains why the two kinds of mail should be kept apart even when one product sends both.
If the question you arrived with was how to “send newsletter programmatically”, the reason is probably the same as for any other part of your product: the audience already lives in your database, the trigger is already an event you own, and a monthly job that assembles a digest and posts it is more reliable than a person remembering to press a button.
Contacts, topics and segments
Three objects, and it pays to be precise about which is which, because the consent lives on one of them and the audience on another.
| Contact | Topic | Segment | |
|---|---|---|---|
| What it is | An address, optional names, and typed properties you declared | A subscription category with a default_subscription | A saved filter over contacts, or a hand-built list, or both |
| What it records | Who the person is | What each person agreed to hear about, as a consent ledger | Which people a broadcast goes to |
| When it changes | On every write; an existing address is updated, never duplicated | Never: the default is fixed at creation | Never: the filter is immutable, membership is evaluated at send time |
| Shown to the reader | No | Yes, on the hosted preference page | No |
| Best for | Holding the properties a merge variable prints | Letting someone leave the digest without leaving the security bulletins | Naming the audience of one broadcast so its report keeps meaning the same people |
The rule that follows from the last row is the one people trip on: a segment’s filter never changes. Rename it freely; for a different audience, create a new one. That is what lets a report on a broadcast you sent last month keep naming the audience it actually went to. The segments reference lists the fields a filter can test and the operators each accepts.
Sending a newsletter, step by step
Every call reads the key from RASKET_API_KEY and sets a User-Agent, because a request without one is refused. Ids from earlier steps are held in shell variables.
- Create a topic — POST /topics with a name, a description for the preference page and a default_subscription. For a newsletter people sign up to, the default is opt_out, and it cannot be changed after creation.
- Add contacts with their consent — POST /contacts with the address, any properties, and topics: [{ id, subscription: "opt_in" }]. The topic entry writes a consent record with its source and time; an address you already hold is updated rather than duplicated.
- Create a segment for the audience — POST /segments with a filter whose condition is { topic, subscription: "opt_in" }. Membership is evaluated when the segment is read and when a broadcast sends, never stored, and the filter itself never changes.
- Create the broadcast — POST /broadcasts with the segment_id, a from address on a verified domain, a subject, and either html carrying {{{FIRST_NAME|there}}} and {{{UNSUBSCRIBE_URL}}} or template: { id } naming a published template.
- Check the gate, then send — GET /broadcasts/{id}/checklist shows every condition of the send gate, passed or not. When all pass, POST /broadcasts/{id}/send with an Idempotency-Key, now or with a scheduled_at up to thirty days out.
- Read the report — GET /broadcasts/{id}/recipients?type= for who was sent, delivered, opened, clicked, bounced, complained, unsubscribed or suppressed, and /clicked-links for every URL with its total and unique clicks.
1. Create a topic
curl https://api.rasket.com/topics \ -H "Authorization: Bearer $RASKET_API_KEY" \ -H "User-Agent: acme-billing/1.0" \ -H "Content-Type: application/json" \ -d '{ "name": "Product updates", "description": "What shipped, once a month.", "default_subscription": "opt_out", "visibility": "public" }'default_subscription is what a contact with no recorded choice is treated as, and it cannot be changed later. For a newsletter people sign up to, opt_out is the honest default: nobody is in until they said so. public lists it on every contact’s preference page.
2. Add contacts with their consent
curl https://api.rasket.com/contacts \ -H "Authorization: Bearer $RASKET_API_KEY" \ -H "User-Agent: acme-billing/1.0" \ -H "Content-Type: application/json" \ -d '{ "email": "ronald.williams@example.com", "first_name": "Ronald", "topics": [{ "id": "'"$TOPIC_ID"'", "subscription": "opt_in" }] }'The topics entry is the consent. Every subscription change writes a record with its source and time, which is what you will point at if anyone asks how this person came to be on the list. An address you already hold is updated and its id returned, so this call is safe to repeat. Where the opt-in came from — a form, and ideally a confirmed one — is your side of the record.
3. Create a segment
curl https://api.rasket.com/segments \ -H "Authorization: Bearer $RASKET_API_KEY" \ -H "User-Agent: acme-billing/1.0" \ -H "Content-Type: application/json" \ -d '{ "name": "Product updates, opted in", "filter": { "all": [{ "topic": "'"$TOPIC_ID"'", "subscription": "opt_in" }] } }'A filter is a tree of all, any and not groups over conditions, and a condition is either a field test or a topic subscription. This one is the smallest useful audience: everyone opted in to the topic. Membership is never stored — it is worked out when you read the segment and again when a broadcast sends — so a contact who opts in tomorrow is in tomorrow’s send.
4. Create the broadcast
curl https://api.rasket.com/broadcasts \ -H "Authorization: Bearer $RASKET_API_KEY" \ -H "User-Agent: acme-billing/1.0" \ -H "Content-Type: application/json" \ -d '{ "name": "September product update", "segment_id": "'"$SEGMENT_ID"'", "topic_id": "'"$TOPIC_ID"'", "from": "Acme <news@send.acme.example>", "subject": "What shipped in September", "html": "<p>Hi {{{FIRST_NAME|there}}},</p><p>Three things shipped this month.</p><p><a href=\"{{{UNSUBSCRIBE_URL}}}\">Unsubscribe</a></p>" }'The body carries two merge variables. {{{FIRST_NAME|there}}} prints the contact’s first name with a fallback, and {{{UNSUBSCRIBE_URL}}} is the one-click link. Any declared contact property works the same way, as do EMAIL and PREFERENCES_URL. If you would rather keep the content out of the request, pass "template": { "id": "monthly-digest" } naming a published template instead of html. Without send: true this is a draft, which is what you want when somebody is going to read it first.
5. Check the gate, then send
# Read the gate firstcurl https://api.rasket.com/broadcasts/$BROADCAST_ID/checklist \ -H "Authorization: Bearer $RASKET_API_KEY" \ -H "User-Agent: acme-billing/1.0"
# Then queue it — now, or at a time you choosecurl https://api.rasket.com/broadcasts/$BROADCAST_ID/send \ -H "Authorization: Bearer $RASKET_API_KEY" \ -H "User-Agent: acme-billing/1.0" \ -H "Idempotency-Key: newsletter-2026-09-send" \ -H "Content-Type: application/json" \ -d '{ "scheduled_at": "2026-09-17T09:00:00Z" }'The checklist answers with every condition of the send gate, passed or not, in the order a send checks them: a from address on a verified domain, a postal address on file, a segment that resolves, a body. A failed condition carries the same name and message the send would refuse with, so you can fix it without a failed attempt. The send itself takes an optional scheduled_at between one minute and thirty days out; omit it to queue now. A scheduled broadcast can be cancelled with POST /broadcasts/{id}/cancel until it starts.
6. Read the report
curl "https://api.rasket.com/broadcasts/$BROADCAST_ID/recipients?type=clicked&limit=20" \ -H "Authorization: Bearer $RASKET_API_KEY" \ -H "User-Agent: acme-billing/1.0"
curl https://api.rasket.com/broadcasts/$BROADCAST_ID/clicked-links \ -H "Authorization: Bearer $RASKET_API_KEY" \ -H "User-Agent: acme-billing/1.0"type is one of sent, delivered, opened, clicked, bounced, complained, unsubscribed or suppressed, and the last one names the reason each person was left out. clicked-links lists every URL with its total and unique clicks. Because each recipient is an ordinary send, the same facts also arrive as webhook events per email, which is how a complaint reaches your own database the moment it happens.
The compliance the API enforces
Some of the rules of bulk mail are yours to keep. The ones below are kept for you, because a broadcast that breaks them should not be possible to send.
- A postal address on file. The gate refuses a send until your team has one. In the United States, the FTC’s CAN-SPAM compliance guide lists a valid physical postal address among what every commercial message must carry, alongside honest header information, a non-deceptive subject line, a way to opt out and prompt honouring of the opt-out. This is not legal advice; read the guide for the detail and your own jurisdiction’s rules for the rest.
- An unsubscribe link in the body. Put
{{{UNSUBSCRIBE_URL}}}or{{{PREFERENCES_URL}}}where you want it. A body with neither gets a footer appended carrying an unsubscribe link and the postal address, so no broadcast can leave without one. - One-click unsubscribe headers. Every broadcast leaves with the header RFC 2369 defined and the one RFC 8058 added, which together let a mail app show its own unsubscribe control and act on it with a single POST.
- A consent ledger. Opt-ins written over the API, opt-outs from the headers and the preference page: all of it lands in one record per contact and topic, with a source and a time, and a broadcast scoped to a topic goes only to contacts whose effective subscription is
opt_in.
List-Unsubscribe: <https://www.rasket.com/u/v1.1.eyJ0…>
List-Unsubscribe-Post: List-Unsubscribe=One-ClickThe headers are the part mailbox providers now check. Google’s sender guidelines require bulk senders to support one-click unsubscribe and to honour it within two days, and the header pair above is how that requirement is met on the wire. The reader’s click is honoured immediately and written to the ledger against the topic, not against their whole relationship with you.
Keep the newsletter off your transactional reputation
Mailbox providers score the domain and address mail comes from, on how recipients treat it. A newsletter draws more complaints than a receipt, because some people forget they subscribed. If both leave from the same domain, the complaints are spent from one balance, and the first symptom is password resets landing in spam for customers who never saw the campaign.
Two separations keep that from happening. The first is Rasket’s: broadcasts are metered separately from transactional volume, so a large send answers marketing_quota_exceeded rather than eating the quota a receipt needs. The second is yours: verify a second sending domain and send the newsletter from it.
One subdomain each
Send receipts and resets from send.acme.example and the newsletter from news.acme.example. They are separate sending domains, separately signed and separately scored, so a bad month for the digest is a bad month for the digest and nothing else. The domains page covers adding the second one; it takes the same records as the first.
One last habit that costs nothing: read the complained and unsubscribed recipients after every send, not just the opens. Those two lists are the audience telling you what they did not want, and a list that shrinks by the people who did not want it is a list whose next send will land. The glossary entry defines the terms this post used in passing, and the broadcasts reference has every field of every call above.
Frequently asked questions
What is the difference between a newsletter API and a transactional email API?
The transport is the same: each recipient of a broadcast is one ordinary email through the same pipeline. The difference is the rules applied before the send: the recipient must have opted in, the message must carry an unsubscribe and a postal address, and the whole thing is metered separately so it cannot exhaust the quota a receipt needs.
Can I send a broadcast from a template instead of inline HTML?
Yes. Pass template: { id } naming a published template in place of html and text, and the broadcast copies that template's published version. Merge variables such as {{{FIRST_NAME|there}}} and {{{UNSUBSCRIBE_URL}}} work the same way in either.
What stops a broadcast from sending?
Four conditions, checked in order: the from address is on a domain verified for sending, the team has a postal address on file, the segment resolves, and the broadcast has a body. A failed condition is a 422 that creates nothing, and GET /broadcasts/{id}/checklist reports the same conditions beforehand.
Do I have to add an unsubscribe link myself?
You should place {{{UNSUBSCRIBE_URL}}} or {{{PREFERENCES_URL}}} where you want it, but a body with neither gets a footer appended with an unsubscribe link and your postal address. Separately, every broadcast carries the RFC 8058 List-Unsubscribe headers, whatever the body says.
Can I change a segment after I have sent to it?
You can rename it, but its filter never changes. A different audience is a new segment, which is what lets the report on a broadcast you sent last month keep naming the people it actually went to. Membership is evaluated at send time, so new opt-ins are picked up automatically.
Why should newsletters go out from a different subdomain?
Mailbox providers score the domain mail comes from on how recipients treat it, and a newsletter draws more complaints than a receipt. Sending the digest from news.acme.example and receipts from send.acme.example keeps the two scores apart, so a bad campaign cannot push password resets into spam.
Sources
- RFC 8058: Signaling One-Click Functionality for List Email Headers — RFC Editor, read 2026-09-16
- RFC 2369: The Use of URLs as Meta-Syntax for Core Mail List Commands — RFC Editor, read 2026-09-16
- CAN-SPAM Act: A Compliance Guide for Business — Federal Trade Commission, read 2026-09-16
Related
- Broadcasts — An audience you own: contacts with typed properties, segments, topics people subscribe to, and broadcasts sent through the same pipeline as your other mail.
- Broadcasts — One message to a segment: the draft, the gate, the send and the results.
- Segments — Audiences defined by a filter, by hand, or both.
- Broadcast — A broadcast is one message sent to many people at once: a newsletter, a product announcement, a release note. What separates it from transactional mail is who…
- Transactional vs marketing email: how to send each — What separates transactional email from marketing email — the trigger, consent, unsubscribe headers and reputation — and how to send both from one API.