# 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**

```bash
curl -X POST https://api.hatang.dev/contacts \
  -H "Authorization: Bearer $HATANG_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "email": "lan@example.net",
    "first_name": "Lan",
    "last_name": "Tran",
    "properties": { "plan": "pro" },
    "segments": [{ "id": "aud_0199e6c17a2b7f009c1e3f6a2b1d9e10" }],
    "topics": [{ "id": "topic_0199e6c17a2c7c418f0e61f1b2a3c4d5", "subscription": "opt_in" }]
  }'
# { "object": "contact", "id": "con_…" }
```

**Node.js**

```ts
const { data, error } = await hatang.contacts.create({
  email: "lan@example.net",
  first_name: "Lan",
  last_name: "Tran",
  properties: { plan: "pro" },
  segments: [{ id: "aud_0199e6c17a2b7f009c1e3f6a2b1d9e10" }],
  topics: [{ id: "topic_0199e6c17a2c7c418f0e61f1b2a3c4d5", subscription: "opt_in" }],
});
```

**Python**

```python
requests.post(
    "https://api.hatang.dev/contacts",
    headers={"Authorization": f"Bearer {os.environ['HATANG_API_KEY']}"},
    json={
        "email": "lan@example.net",
        "first_name": "Lan",
        "properties": {"plan": "pro"},
        "segments": [{"id": "aud_0199e6c17a2b7f009c1e3f6a2b1d9e10"}],
    },
)
```

**PHP**

```php
$client->post('/contacts', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('HATANG_API_KEY')],
    'json' => [
        'email' => 'lan@example.net',
        'first_name' => 'Lan',
        'properties' => ['plan' => 'pro'],
        'segments' => [['id' => 'aud_0199e6c17a2b7f009c1e3f6a2b1d9e10']],
    ],
]);
```

| Field | Description |
|---|---|
| `email` | Required. Unique in your business. |
| `first_name`, `last_name` | Up to 100 characters each. |
| `unsubscribed` | `true` unsubscribes the contact from everything. |
| `properties` | Values for declared properties. `null` removes a value. |
| `segments` | Up to 50 `{ "id" }` to add the contact to. |
| `topics` | Up 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](https://hatang.dev/docs/guides/email/automations.md).

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:

```bash
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

```bash
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/lan@example.net/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

```bash
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" }'
```

| Field | Description |
|---|---|
| `name` | Up to 50 characters. |
| `description` | Up to 200 characters, shown on the preferences page. |
| `default_subscription` | `opt_in`: contacts get it until they leave. `opt_out`: contacts get it only after they subscribe. Fixed once created. |
| `visibility` | `public` 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`:

```bash
curl -X PATCH https://api.hatang.dev/contacts/lan@example.net/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](https://hatang.dev/docs/guides/email/broadcasts.md#unsubscribe).

## API reference

| Endpoint | Description |
|---|---|
| `POST /contacts`, `GET /contacts` | Create 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}/topics` | Topic choices. |
| `POST /segments`, `GET /segments`, `GET`, `PATCH`, `DELETE /segments/{id}`, `GET /segments/{id}/contacts` | Segments. `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}/contacts` | The 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

- [Broadcasts](https://hatang.dev/docs/guides/email/broadcasts.md)
- [Automations](https://hatang.dev/docs/guides/email/automations.md)
- [Suppressions and bounces](https://hatang.dev/docs/guides/email/suppressions-and-bounces.md)

Source: https://hatang.dev/docs/guides/email/audiences
