# Segments (/concepts/segments)

A segment is a named group of contacts you can point campaigns at or use to kick off workflows. Segments come in two flavours: **Dynamic** and **Static**.

## Dynamic vs Static

|                            | Dynamic                                                  | Static                                         |
| -------------------------- | -------------------------------------------------------- | ---------------------------------------------- |
| Membership                 | Derived live from filter conditions                      | Hand-picked — you choose every member          |
| `condition` field          | Required — holds the filter definition                   | Must be omitted                                |
| Add/remove members via API | Not possible (membership follows the filter)             | `POST` / `DELETE` `/segments/:id/members`      |
| Membership recomputation   | Recalculated in the background when tracking is on       | N/A — changes only through explicit API calls  |
| Entry/exit events          | Emitted only with **Track membership changes** turned on | Never emitted by add/remove calls              |
| Updating the filter        | Triggers a member count recalculation                    | `condition` on an update is silently discarded |

Reach for dynamic segments when membership should follow behaviour ("Pro-plan subscribers who opened any email in the past 14 days"). Reach for static segments when the list is curated by hand — beta testers, event attendees, or contacts pulled in from another system.

## Filtering on a dynamic segment

Dynamic membership is driven by a filter: a set of conditions matched against every contact. Conditions can reference:

* The built-in fields `email`, `subscribed`, `createdAt`, and `updatedAt`.
* Any custom field you've saved on contacts (everything under `data.*`).
* Events you've tracked through `/v1/track` (`event.signed_up`, `event.purchased`, and so on).
* Engagement signals such as `email.opened`, `email.clicked`, and `email.bounced`.
* Whether the contact belongs to some other segment.

Conditions combine with `AND` / `OR` and can be nested into groups, so audiences like "subscribed Pro users **and** (opened **or** clicked an email in the last 14 days)" are expressible.

Every available field, operator, and value type — with worked examples — is documented in the [Segment filter reference](/guides/segment-filters).

## Static segments

A static segment is a list you maintain yourself. Nothing joins or leaves it automatically; its membership is exactly what you've put in it.

### Creating

In the dashboard, open **Segments**, hit **Create Segment**, and pick **Static**. If you like, seed it with initial members right away via the contact search.

### Adding and removing members

Inside a static segment, the **Add Members** search finds contacts to include. It only surfaces contacts that already exist in the project — so there's no way to add a nonexistent one by mistake — and members already in the segment appear greyed out.

Via the API:

* `POST /segments/:id/members` — add contacts by email. Body: `{ emails: string[], createMissing?: boolean, subscribed?: boolean }`. Setting `createMissing: true` creates any missing contacts on the spot (subscribed by default, unless `subscribed: false` is passed). The response summarises `{ added, created, notFound }`.
* `DELETE /segments/:id/members` — remove contacts by email. Body: `{ emails: string[] }`. Responds with `{ removed }`.

These two endpoints work **exclusively** on static segments — pointing them at a dynamic one yields `400`.

API-driven membership changes take effect at once, but they never emit `entry`/`exit` events; those exist only for dynamic segments with tracking enabled.

## Tracking membership changes

A dynamic segment can enable **Track membership changes**. With it on, Bitelio emits an event each time a contact joins or leaves:

* `segment.<slug>.entry` — contact joined
* `segment.<slug>.exit` — contact left

Bitelio builds `<slug>` from the segment's name: everything lowercased, accents and punctuation removed, spaces turned into hyphens, and runs of hyphens collapsed to one. So `"VIP Customers"` yields `segment.vip-customers.entry`. Choose names whose slugs won't need to change — a rename means a new event name.

These events are a natural way to power workflows: greet contacts entering a "Trial users" segment, or win back those leaving "Active users". The payload format is documented under [Webhooks](/guides/webhooks).

### How tracking works

Creating a dynamic segment or editing its filter refreshes the member count right away. From then on, membership is recomputed in the background at a regular interval: Bitelio diffs the current matches against the last known set, logs who entered and who left, and fires the matching events.

That background interval means the dashboard's `memberCount` can trail reality by a few minutes. When you need an up-to-date number, or want `entry` / `exit` events to fire without waiting:

* `POST /segments/:id/refresh` — recount only (inexpensive, emits nothing).
* `POST /segments/:id/compute` — run a complete membership recomputation, flushing any pending entry/exit events.

## Using segments

Segments plug into two places:

* As campaign audiences in [email campaigns](/concepts/campaigns) (point the campaign's audience at the segment)
* As workflow triggers in [marketing automation](/concepts/workflows) (listen for the `segment.<slug>.entry` event)

## Performance notes

* Date comparisons on `data.*` fields work by ordering ISO 8601 strings. To use `within` / `olderThan` on a custom date, store it as an ISO 8601 string (`2026-05-06T12:00:00Z`), not as a Unix timestamp.
* Chaining many untracked dynamic segments inside each other makes evaluation expensive. When one segment is referenced by others, turn on **Track membership** for it — its member list is then read directly instead of being recomputed on every evaluation.
* `memberCount` is a cache that may run a few minutes behind. Treat it as an estimate, and hit `POST /segments/:id/refresh` when an exact figure matters.

## Deleting a segment

If a segment backs an active campaign (one in `DRAFT`, `SCHEDULED`, or `SENDING`), deleting it fails with `409 Conflict`. Either cancel that campaign or switch its audience before removing the segment.

## API reference

All segment endpoints — list, get, create, update, delete, list members, add/remove static members, refresh count, and compute membership — are catalogued in the [API overview](/api-reference/overview#segments).

## What's next

<Cards>
  <Card title="Filter reference" href="/guides/segment-filters">
    Every filter field, operator, and value type in detail.
  </Card>

  <Card title="Custom fields" href="/guides/custom-fields">
    How `data.*` fields get their types and behave in filters.
  </Card>

  <Card title="Campaigns" href="/concepts/campaigns">
    Point a campaign's audience at a segment.
  </Card>

  <Card title="Workflows" href="/concepts/workflows">
    Kick off workflows from segment entry / exit events.
  </Card>
</Cards>
