Skip to main content

Contacts, segments and topics

Before you send a broadcast, you need people to send it to. Ha Tang organizes recipients the way Resend does today:

  • A contact is one email address in your business (case does not matter), with a first and last name, custom properties and an "unsubscribed from everything" flag.
  • A segment is a fixed group of contacts you send broadcasts to. A contact can be in many segments; deleting a segment keeps its contacts. Segments used to be called audiences, and the /audiences API still works.
  • A topic is a kind of email, such as promotions or a weekly newsletter, that each recipient can subscribe to or leave on the preferences page.

In the dashboard, everything is under Email → Audiences, in the Contacts, Segments, Topics, Properties and Suppressions tabs.

Before you start​

The contact, segment, topic and property endpoints need a key with email:write.

Add contacts​

curl -X POST https://api.hatang.dev/contacts \
-H "Authorization: Bearer $HATANG_API_KEY" -H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"first_name": "Lan",
"last_name": "Tran",
"properties": { "plan": "pro" },
"segments": [{ "id": "aud_0199e6c17a2b7f009c1e3f6a2b1d9e10" }],
"topics": [{ "id": "topic_0199e6c17a2c7c418f0e61f1b2a3c4d5", "subscription": "opt_in" }]
}'
# { "object": "contact", "id": "con_…" }
FieldDescription
emailRequired. Unique in your business.
first_name, last_nameUp to 100 characters each.
unsubscribedtrue unsubscribes the contact from everything.
propertiesValues for declared properties. null removes a value.
segmentsUp to 50 { "id" } to add the contact to.
topicsUp to 50 { "id", "subscription" } pairs, where subscription is opt_in or opt_out.

POST /contacts creates the contact, or updates it when the address already exists, and returns its id. The contact.created, contact.updated and contact.deleted webhooks fire on changes. A new contact also starts automations triggered by hatang:contact.created; see Automations.

In the dashboard you can also Add contact one by one or Import CSV. The first row of the CSV is the header with any of these columns: email, first_name, last_name, unsubscribed and your property keys. Other columns are ignored and existing addresses are updated. Customers in the CRM can be synced too (Customers → Sync to Email); syncing never resubscribes someone who unsubscribed.

Custom properties​

Declare a property before you set it on contacts. A property is a string or a number, with an optional fallback value:

curl -X POST https://api.hatang.dev/contact-properties \
-H "Authorization: Bearer $HATANG_API_KEY" -H "Content-Type: application/json" \
-d '{ "key": "plan", "type": "string", "fallback_value": "free" }'
  • Keys use letters, digits and _, up to 50 characters. String values are up to 2,000 characters.
  • Setting a property that is not declared returns 422. A number property refuses values that are not numbers.
  • Only the fallback can change later (PATCH /contact-properties/{id}). Deleting a property removes its value from every contact.

Use properties in broadcasts and automation emails as {{{contact.plan}}}, next to {{{contact.first_name}}}, {{{contact.last_name}}} and {{{contact.email}}}. A contact without a value gets the inline default ({{{contact.plan|free}}}), then the property's fallback.

Segments​

curl -X POST https://api.hatang.dev/segments \
-H "Authorization: Bearer $HATANG_API_KEY" -H "Content-Type: application/json" \
-d '{ "name": "Customers 2026" }'
# { "object": "segment", "id": "aud_…", "name": "Customers 2026", "created_at": "…" }

curl -X POST https://api.hatang.dev/contacts/[email protected]/segments/aud_0199e6c17a2b7f009c1e3f6a2b1d9e10 \
-H "Authorization: Bearer $HATANG_API_KEY"

Segment names are up to 100 characters. Segment ids start with aud_, because a segment is what used to be an audience.

Topics and the preferences page​

curl -X POST https://api.hatang.dev/topics \
-H "Authorization: Bearer $HATANG_API_KEY" -H "Content-Type: application/json" \
-d '{ "name": "Promotions", "description": "Sales and discount codes", "default_subscription": "opt_in", "visibility": "public" }'
FieldDescription
nameUp to 50 characters.
descriptionUp to 200 characters, shown on the preferences page.
default_subscriptionopt_in: contacts get it until they leave. opt_out: contacts get it only after they subscribe. Fixed once created.
visibilitypublic shows the topic to everyone on the preferences page. private (the default) shows it only to contacts subscribed to it.

How topics filter mail:

  • A broadcast with a topic_id only reaches contacts subscribed to that topic.
  • POST /emails with a topic_id skips recipients who left that topic; they are reported as suppressed. Addresses that are not contacts follow the topic's default.
  • A contact's choices are in GET /contacts/{id}/topics and change with PATCH /contacts/{id}/topics:
curl -X PATCH https://api.hatang.dev/contacts/[email protected]/topics \
-H "Authorization: Bearer $HATANG_API_KEY" -H "Content-Type: application/json" \
-d '[{ "id": "topic_0199e6c17a2c7c418f0e61f1b2a3c4d5", "subscription": "opt_out" }]'

The {{{UNSUBSCRIBE_URL}}} link in a broadcast opens the preferences page, where the recipient switches each topic on or off or unsubscribes from everything. Opening the page changes nothing, so link scanners cannot unsubscribe anyone by accident. The page is in English when the browser prefers English and in Vietnamese otherwise. Set its title, description, logo and colors under Email settings → Unsubscribe page. How one-click unsubscribe works is in Broadcasts.

API reference​

EndpointDescription
POST /contacts, GET /contactsCreate or update a contact; list contacts (limit, after, segment_id, q).
GET, PATCH, DELETE /contacts/{id or email}One contact.
GET /contacts/{id}/segments, POST or DELETE /contacts/{id}/segments/{segment_id}Segment membership.
GET, PATCH /contacts/{id}/topicsTopic choices.
POST /segments, GET /segments, GET, PATCH, DELETE /segments/{id}, GET /segments/{id}/contactsSegments. GET /segments/{id} includes contact_count.
POST /topics, GET /topics, GET, PATCH, DELETE /topics/{id}Topics.
POST /contact-properties, GET /contact-properties, PATCH, DELETE /contact-properties/{id}Properties.
/audiences, /audiences/{id}/contactsThe older audience API, kept for existing Resend and Ha Tang clients. An audience is a segment.

The Node.js SDK has the same calls under hatang.contacts, hatang.segments, hatang.topics, hatang.contactProperties and hatang.audiences.

Next steps​

View this page as Markdown