Skip to main content

Broadcasts

A broadcast is a marketing email, such as a newsletter, a sale or a product update, sent to every contact of a segment. Ha Tang turns it into one email per contact, fills in each contact's name and properties, skips people who unsubscribed and adds an unsubscribe link and a one-click List-Unsubscribe header.

Before you start​

  • A verified domain for the From address. See Domains.
  • A segment with contacts, and topics if recipients should be able to pick what they receive. See Contacts, segments and topics.
  • For the API, a key with email:write.

How a broadcast picks its recipients​

A contact receives the broadcast when all of these are true:

  1. The contact is in the broadcast's segment.
  2. The contact has not unsubscribed from everything.
  3. If the broadcast has a topic: the contact is subscribed to it. For an opt_in topic, that means the contact has not left it; for an opt_out topic, the contact must have joined it.
  4. The address is not on the suppression list (hard bounce, complaint, manual block). Suppressed addresses are skipped and reported as bounced. See Suppressions and bounces.

Compose​

In the dashboard, open Email → Broadcasts → New broadcast:

  • Segment: who receives it. Topic: optional; No topic (everyone subscribed) sends to every subscribed contact of the segment.
  • Internal name, From and Subject.
  • Start from a template copies a published template's HTML, text, sender and subject into the broadcast.
  • The HTML body and an optional Plain text version, with Preview.

Save it with Create draft. You can change a broadcast while it is a draft or scheduled.

Merge tags​

Merge tags are filled for each contact:

TagValue
{{{FIRST_NAME}}}, {{{LAST_NAME}}}, {{{EMAIL}}}The contact's name and address.
{{{contact.first_name}}}, {{{contact.plan}}}…Contact fields and custom properties.
{{{UNSUBSCRIBE_URL}}}The contact's preferences page. {{{RESEND_UNSUBSCRIBE_URL}}} is accepted too.

Add a default after |: Hi {{{FIRST_NAME|there}}}. A property without a value uses its fallback. A tag with no value at all becomes empty; it does not stop the send. Values are HTML-escaped in the HTML body.

<p>Hi {{{FIRST_NAME|there}}},</p>
<p>Your {{{contact.plan|free}}} plan now includes 10 GB of storage.</p>
<p><a href="{{{UNSUBSCRIBE_URL}}}">Unsubscribe or choose what you receive</a></p>

Create with the API​

curl -X POST https://api.hatang.dev/broadcasts \
-H "Authorization: Bearer $HATANG_API_KEY" -H "Content-Type: application/json" \
-d '{
"name": "October newsletter",
"segment_id": "aud_0199e6c17a2b7f009c1e3f6a2b1d9e10",
"topic_id": "topic_0199e6c17a2c7c418f0e61f1b2a3c4d5",
"from": "Acme <[email protected]>",
"subject": "What is new in October, {{{FIRST_NAME|friend}}}",
"html": "<p>Hi {{{FIRST_NAME|there}}}, …</p><p><a href=\"{{{UNSUBSCRIBE_URL}}}\">Unsubscribe</a></p>"
}'
# { "object": "broadcast", "id": "bc_…" }
FieldDescription
segment_idRequired. The segment to send to. audience_id is the older name and still works.
topic_idOptional. Only contacts subscribed to this topic.
template_idOptional. Copy html, text, from, subject and reply_to from this published template. Fields you send win.
fromRequired unless the template sets it.
subjectRequired unless the template sets it. Up to 998 characters.
reply_toOne address or a list.
html, textThe body. At least one is needed before you send.
nameInternal name, up to 200 characters. Defaults to the subject.

The template is copied when the broadcast is created; publishing a new template version later does not change it.

Send or schedule​

In the editor, the Send box takes now, an ISO date or a phrase such as in 2 hours. Save your changes before you click Send broadcast. With the API:

# Now
curl -X POST https://api.hatang.dev/broadcasts/$BROADCAST_ID/send -H "Authorization: Bearer $HATANG_API_KEY"

# Later
curl -X POST https://api.hatang.dev/broadcasts/$BROADCAST_ID/send \
-H "Authorization: Bearer $HATANG_API_KEY" -H "Content-Type: application/json" \
-d '{ "scheduled_at": "2026-10-20T09:00:00+07:00" }'
# { "object": "broadcast", "id": "bc_…", "status": "scheduled" }

scheduled_at takes the same values as for single emails: an ISO 8601 date up to 30 days ahead, or in N minutes|hours|days. Sending a scheduled broadcast again replaces its schedule.

POST /broadcasts/{id}/cancel stops a broadcast that is scheduled or still queued. A broadcast that is sending cannot be canceled or deleted.

StatusMeaning
draftNot sent yet. You can edit it.
scheduledWaiting for its time. You can still edit or cancel it.
queuedAbout to start.
sendingEmails are being created for each contact.
sentEvery recipient has been handled.
canceledCanceled before it started.

GET /broadcasts/{id} shows total (contacts selected) and sent_count (emails created). Each email of a broadcast is a normal email with the tag broadcast_id, so you can follow it under Email → Emails (filter Broadcast), in GET /emails?source=broadcast and through the usual webhooks. If sending is interrupted, Ha Tang picks up where it stopped and never sends a contact the same broadcast twice.

Quota

Every email of a broadcast counts toward your daily quota. A broadcast to more contacts than the quota you have left stops sending when the quota runs out; the remaining contacts are marked as failed. Check Email settings before a large send.

Unsubscribe​

Every broadcast email carries two ways out:

  • The {{{UNSUBSCRIBE_URL}}} link in your content opens the hosted preferences page, where the contact picks topics or unsubscribes from everything. Opening it changes nothing by itself.
  • A List-Unsubscribe header with List-Unsubscribe-Post: List-Unsubscribe=One-Click (RFC 8058), so Gmail, Apple Mail and others show their own unsubscribe button. One click removes the contact from the broadcast's topic, or from everything when the broadcast has no topic.

Ha Tang adds the header for you. Put the link in your content as well: some mail clients do not show the button, and the law in many countries requires a visible way to unsubscribe.

When a contact unsubscribes from everything, they stop receiving broadcasts and automation emails, and their address is added to the suppression list with the reason unsubscribe, which also blocks other emails to it. See Suppressions and bounces.

Brand the preferences page under Email settings → Unsubscribe page: title, description, logo and colors.

API reference​

EndpointDescription
POST /broadcastsCreate a draft. Returns 201 with the id.
GET /broadcasts, GET /broadcasts/{id}List, or get one with status, total, sent_count, scheduled_at and sent_at.
PATCH /broadcasts/{id}Change a draft or scheduled broadcast.
POST /broadcasts/{id}/sendSend now or schedule (scheduled_at).
POST /broadcasts/{id}/cancelCancel a scheduled or queued broadcast.
DELETE /broadcasts/{id}Delete it, unless it is sending.

Next steps​

View this page as Markdown