> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tracklysms.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Segmentation Logic

> Master AND/OR logic for complex audience targeting

Trackly SMS supports sophisticated audience segmentation with nested AND/OR logic. This guide explains how to combine conditions effectively.

## Logic Basics

### AND (All Must Match)

When conditions are combined with AND, a contact must satisfy **every** condition:

```
last_clicked_at within 7 days
AND send_count gt 3
AND custom_fields.state eq "California"
```

A contact must have:

* Clicked in the last 7 days, AND
* Received more than 3 messages, AND
* Have `custom_fields.state` equal to "California"

**Result**: Only contacts meeting ALL criteria are included.

### OR (Any Can Match)

When conditions are combined with OR, a contact must satisfy **at least one** condition:

```
custom_fields.state eq "California"
OR custom_fields.state eq "Texas"
OR custom_fields.state eq "Florida"
```

A contact with `custom_fields.state` matching California, Texas, OR Florida is included.

**Result**: Contacts meeting ANY criterion are included.

## Condition Groups

### Creating Groups

Groups let you combine AND and OR logic. Each group is evaluated first, then groups are combined.

**Structure**:

```
Group 1 (AND):
  - Condition A
  - Condition B

[AND/OR]

Group 2 (AND):
  - Condition C
  - Condition D
```

### Example: Engaged OR New

Target engaged contacts OR brand new signups:

```
Group 1 (AND):
  - last_clicked_at within 7 days
  - click_count gt 2

OR

Group 2 (AND):
  - signup_date within 3 days
  - send_count eq 0
```

**Matches**:

* Recent multi-clickers (engaged users)
* Brand new signups who haven't been messaged

## Common Patterns

### Geographic Targeting

Target multiple states or regions:

```
custom_fields.state eq "California"
OR custom_fields.state eq "Texas"
OR custom_fields.state eq "Florida"
OR custom_fields.state eq "New York"
```

<Tip>
  For many values, consider using the `in` operator (e.g., `custom_fields.state in ["CA", "TX", "FL", "NY"]`) or creating a custom field like `custom_fields.region eq "West"` instead of listing states individually.
</Tip>

### Engagement Tiers

Target by engagement level:

<Tabs>
  <Tab title="Highly Engaged">
    ```
    last_clicked_at within 7 days
    AND click_count gt 5
    ```
  </Tab>

  <Tab title="Moderately Engaged">
    ```
    last_clicked_at within 30 days
    AND click_count gte 2
    AND click_count lte 5
    ```
  </Tab>

  <Tab title="At-Risk">
    ```
    last_clicked_at not_within 30 days
    AND last_sent_at within 30 days
    AND click_count gt 0
    ```

    (Previously engaged but went quiet)
  </Tab>
</Tabs>

### Revenue-Based Segments

<Tabs>
  <Tab title="High Value">
    ```
    revenue_total gt 100
    ```
  </Tab>

  <Tab title="Has Purchased">
    ```
    has_revenue eq true
    ```
  </Tab>

  <Tab title="No Purchase Yet">
    ```
    has_revenue eq false
    AND click_count gt 3
    ```

    (Engaged but hasn't converted)
  </Tab>
</Tabs>

### Reply-Based Segments

Target contacts by their inbound replies — what they said, when they last replied, and how often. These conditions read the contact's reply history captured in the [Inbox](/guides/inbox/overview).

| Field                          | Type   | Operators                              |
| ------------------------------ | ------ | -------------------------------------- |
| `last_reply_text` (Reply Text) | text   | `eq`, `ne`, `contains`, `not_contains` |
| `last_reply_at` (Last Reply)   | date   | `within`, `not_within`, `gt`, `lt`     |
| `reply_count` (Reply Count)    | number | `eq`, `gt`, `gte`, `lt`, `lte`         |

<Tabs>
  <Tab title="Said a keyword">
    ```
    reply_text contains "yes"
    ```

    (Reply-text matching is case-insensitive.)
  </Tab>

  <Tab title="Active repliers">
    ```
    reply_count gte 2
    AND last_reply_at within 30 days
    ```

    (Contacts who reply often and replied recently.)
  </Tab>

  <Tab title="Asked for help">
    ```
    reply_text contains "help"
    ```

    (Surface contacts who need follow-up.)
  </Tab>
</Tabs>

<Note>
  Reply conditions match against the contact's **latest** reply text and aggregate reply counts on `ListContact`. `ne` / `not_contains` require a reply to exist (contacts who never replied are excluded).
</Note>

### Re-Engagement

Target lapsed contacts who were once active:

```
Group 1 (AND):
  - last_sent_at within 60 days
  - last_clicked_at not_within 30 days

AND

Group 2 (OR):
  - click_count gt 5
  - has_revenue eq true
```

**Matches**: Contacts who:

* Have been messaged recently
* Haven't clicked in a month
* BUT previously showed strong engagement

### Exclusion Logic

Exclude certain contacts using NOT conditions:

```
signup_date within 30 days
AND active eq true
AND last_sent_at not_within 24 hours
```

**Matches**: New signups who are active (not unsubscribed) and haven't been messaged today.

## Advanced: Nested Groups

For complex logic, nest groups within groups:

```
Group A (AND):
  |-- Group 1 (OR):
  |     - custom_fields.state eq "CA"
  |     - custom_fields.state eq "TX"
  |
  |-- AND
  |
  |-- Group 2 (AND):
        - last_clicked_at within 14 days
        - click_count gt 1
```

**Matches**: Contacts in CA or TX who clicked recently AND clicked more than once.

<Note>
  Deeply nested groups (groups within groups) are supported in the data model but are not currently exposed in the UI. The UI supports one level of group nesting.
</Note>

## Evaluation Order

Conditions are evaluated in this order:

1. Individual conditions within groups
2. Groups combined (AND/OR between groups)
3. Final audience membership

**Example**:

```
(A AND B) OR (C AND D)
```

1. Evaluate A AND B → Result 1
2. Evaluate C AND D → Result 2
3. Result 1 OR Result 2 → Final audience

## Debugging Audiences

### Audience Too Small

If your audience is smaller than expected:

* Check that conditions aren't too restrictive
* Try removing conditions one at a time
* Preview to see what's filtering contacts out
* Verify the field has data (e.g., `custom_fields.state` may be blank)

### Audience Too Large

If your audience is larger than expected:

* You may have OR where you meant AND
* Check group logic (groups OR'd together include more)
* Add additional qualifying conditions

### Preview Tool

Always use the preview before sending:

1. Click **Preview** in the audience builder
2. Review the sample contacts
3. Check if they match your intent
4. Adjust conditions if needed

## Performance Tips

<AccordionGroup>
  <Accordion title="Indexed fields are faster">
    Fields like `last_clicked_at`, `send_count`, and `signup_date` are stored on ListContact and are indexed for fast queries. Custom fields may be slower on large lists.

    **Cross-collection lookups are slowest**: Timezone, carrier, and DNC conditions require a `$lookup` join to the Contact collection, which is significantly slower than ListContact-only conditions. Use these sparingly in complex audiences.
  </Accordion>

  <Accordion title="Fewer groups = faster">
    Simple audiences with 2-3 conditions evaluate faster than deeply nested logic.
  </Accordion>

  <Accordion title="Cache audience sizes">
    For audiences used frequently, the cached size updates periodically. Don't refresh constantly.
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Dynamic vs Static" icon="rotate" href="/guides/audiences/dynamic-vs-static">
    Choose the right audience type
  </Card>

  <Card title="Campaign Targeting" icon="bullseye" href="/guides/campaigns/overview">
    Use audiences in campaigns
  </Card>
</CardGroup>
