Segmentation
Create dynamic contact segments with filter groups, nested logic, and async computation.
Segments let you group contacts based on filters - contact fields, account data, metadata, firmographic attributes, and behavioral events. Segments are computed asynchronously and can be used to target experiments (segmentId). Journeys don't read segments: journey trigger conditions and branch conditions only look at the trigger event's payload.
On api.molted.email, pass tenantId (in the body for writes, in the query string for reads); without it a key call fails with 401 missing_tenant_id. mailbox.molted.email, the CLI and the MCP server fill it in. Writes need manage scope and refuse a mailbox-scoped key.
Create a segment
molted segments create \
--name "Enterprise customers in US" \
--filters '{
"logic": "and",
"filters": [
{ "type": "contact_field", "field": "lifecycleStage", "operator": "eq", "value": "customer" },
{ "type": "metadata", "field": "region", "operator": "eq", "value": "us-west" },
{ "type": "firmographic", "field": "companySize", "operator": "gte", "value": 500 }
]
}'| Flag | Type | Required | Description |
|---|---|---|---|
--name | string | Yes | Segment name. |
--filters | JSON | Yes | Filter group as JSON. See filter reference below. |
POST https://mailbox.molted.email/v1/segmentscurl -X POST https://mailbox.molted.email/v1/segments \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Enterprise customers in US",
"filterGroup": {
"logic": "and",
"filters": [
{ "type": "contact_field", "field": "lifecycleStage", "operator": "eq", "value": "customer" },
{ "type": "metadata", "field": "region", "operator": "eq", "value": "us-west" },
{ "type": "firmographic", "field": "companySize", "operator": "gte", "value": 500 }
]
}
}'| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Segment name. |
filterGroup | object | Yes | Filter group object. See filter reference below. |
Create (and update) return the segment row { id, tenantId, name, filterGroup, status, createdAt, updatedAt }. The filter group is stored in the canonical shape at write time. Two shorthands are also accepted and normalized: { "operator": "and", "conditions": [{ "field", "op", "value" }] }, and a single bare filter. A filter without type is inferred: email, name, ownerEmail, lifecycleStage and dealStage become contact_field, anything else metadata.
Get a segment
molted segments get seg_abc123GET https://mailbox.molted.email/v1/segments/:idcurl "https://mailbox.molted.email/v1/segments/seg_abc123" \
-H "Authorization: Bearer YOUR_API_KEY"Get also returns archived segments, and adds computeDurationMs. An unknown id is 404 segment_not_found.
List segments
molted segments listGET https://mailbox.molted.email/v1/segmentscurl "https://mailbox.molted.email/v1/segments" \
-H "Authorization: Bearer YOUR_API_KEY"Response
Returns an array of active segments. Archived segments are excluded. contactCount and snapshotVersion are null until the segment's first compute (creating a segment doesn't compute it).
[
{
"id": "seg_abc123",
"tenantId": "tenant_abc123",
"name": "Enterprise customers in US",
"filterGroup": { "logic": "and", "filters": [ "..." ] },
"status": "active",
"contactCount": 142,
"snapshotVersion": 3,
"createdAt": "2026-03-01T12:00:00Z",
"updatedAt": "2026-03-15T08:30:00Z"
}
]If you have no segments yet, the response is an empty array:
[]No segments yet? Create your first segment with molted segments create. See creating segments above for filter syntax and examples.
Update a segment
molted segments update seg_abc123 \
--name "Enterprise US (updated)" \
--filters '{
"logic": "and",
"filters": [
{ "type": "contact_field", "field": "lifecycleStage", "operator": "eq", "value": "customer" },
{ "type": "metadata", "field": "region", "operator": "in", "value": ["us-west", "us-east"] }
]
}'| Flag | Type | Required | Description |
|---|---|---|---|
--name | string | No | Updated segment name. |
--filters | JSON | No | Updated filter group as JSON. |
PATCH https://mailbox.molted.email/v1/segments/:idcurl -X PATCH https://mailbox.molted.email/v1/segments/seg_abc123 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Enterprise US (updated)",
"filterGroup": {
"logic": "and",
"filters": [
{ "type": "contact_field", "field": "lifecycleStage", "operator": "eq", "value": "customer" },
{ "type": "metadata", "field": "region", "operator": "in", "value": ["us-west", "us-east"] }
]
}
}'Archive a segment
molted segments archive seg_abc123DELETE https://mailbox.molted.email/v1/segments/:idcurl -X DELETE "https://mailbox.molted.email/v1/segments/seg_abc123" \
-H "Authorization: Bearer YOUR_API_KEY"Archiving is a soft delete: it returns { "archived": true } (or 404), and the segment stays readable with get.
Compute segment membership
Trigger an async recomputation of the segment's current members. Each computation creates a point-in-time snapshot of matching contacts.
Most flows do not need to call this directly: contacts sync automatically enqueues a recompute for every active segment in the tenant, so segments list / segments get reflect the new contacts shortly after the sync. Use this command when you want to force a refresh without syncing contacts (e.g., after manually editing contact metadata, or to verify a freshly created segment).
Create and update already store the canonical { logic, filters, type, operator } shape. Compute still rewrites a legacy filter_group shape (e.g. { operator, conditions, op } from older API versions) to the canonical one, as a safety net for rows written before that change.
The compute call returns { "queued": true, "segmentId": "..." }.
molted segments compute seg_abc123POST https://mailbox.molted.email/v1/segments/:id/computecurl -X POST "https://mailbox.molted.email/v1/segments/seg_abc123/compute" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'List segment members
Results are paginated. Use limit and offset to page through members. Each member is { id, email, name, lifecycleStage, dealStage }, ordered by email.
molted segments members seg_abc123 --limit 50 --offset 0| Flag | Type | Required | Description |
|---|---|---|---|
--limit | number | No | Max results per page (default 50). |
--offset | number | No | Pagination offset (default 0). |
GET https://mailbox.molted.email/v1/segments/:id/memberscurl "https://mailbox.molted.email/v1/segments/seg_abc123/members?limit=50&offset=0" \
-H "Authorization: Bearer YOUR_API_KEY"Get contact segments
List all segments a contact belongs to. This is a reverse lookup -- given a contact, it returns every segment they are a member of.
To check whether a contact is in one specific segment instead, use analytics segment-check.
Returns [{ id, name, status }] for active segments only, or 404 contact_not_found.
molted segments contact con_abc123GET https://mailbox.molted.email/v1/segments/contact/:contactId/segmentscurl "https://mailbox.molted.email/v1/segments/contact/con_abc123/segments" \
-H "Authorization: Bearer YOUR_API_KEY"Filter types
Each filter has a type that determines which fields you can query.
| Type | Description | Queryable fields |
|---|---|---|
contact_field | Filter on contact properties. | email, name, ownerEmail, lifecycleStage, dealStage |
account_field | Filter on the contact's linked account. | name, domain, plus any key in the account's metadata object |
metadata | Filter on contact custom metadata. | Any key in the contact's metadata object |
firmographic | Filter on account custom metadata. | Any key in the account's metadata object |
behavioral | Filter on event counts within a time window. | Uses behavioralWindow - see behavioral filters below |
Standard filter structure
{
"type": "contact_field",
"field": "lifecycleStage",
"operator": "eq",
"value": "customer"
}| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | One of: contact_field, account_field, metadata, firmographic, behavioral. |
field | string | Yes | The field to filter on. Valid fields depend on the type. |
operator | string | Yes | Comparison operator. See operators below. |
value | any | Varies | The value to compare against. Not required for exists/not_exists. |
Behavioral filters
Behavioral filters count how many times an event occurred within a time window. They use a behavioralWindow object instead of a simple value.
{
"type": "behavioral",
"field": "event_count",
"operator": "gte",
"value": 0,
"behavioralWindow": {
"eventName": "email_opened",
"windowDays": 30,
"countOperator": "gte",
"countValue": 3
}
}| Field | Type | Required | Description |
|---|---|---|---|
behavioralWindow.eventName | string | Yes | The ingested event name to count (e.g. email_opened). |
behavioralWindow.windowDays | number | Yes | Lookback window in days. Stored but not applied yet: counts always cover the last 90 days. |
behavioralWindow.countOperator | string | Yes | Operator to apply to the event count (e.g. gte, eq, lt). |
behavioralWindow.countValue | number | Yes | The threshold count to compare against. |
This example matches contacts with 3 or more email_opened events in the window.
Behavioral counts only see events you ingest yourself with POST /v1/agent/events/ingest (molted journeys trigger). Sends, opens and clicks are not recorded there automatically, so email_opened-style names only match if you ingest events with those names. Two more limits today: windowDays is ignored and counts always cover the last 90 days, and a behavioral filter's own field, operator and value are ignored (only behavioralWindow is evaluated).
Operators
| Operator | Value type | Description | Example value |
|---|---|---|---|
eq | any | Equals. | "pro", 500, true |
neq | any | Not equals. | "free" |
gt | number | Greater than. | 100 |
gte | number | Greater than or equal. | 500 |
lt | number | Less than. | 50 |
lte | number | Less than or equal. | 1000 |
contains | string | String contains substring. | "example.com" |
not_contains | string | String does not contain substring. | "test" |
in | array | Value is in the provided list. | ["us-west", "us-east"] |
not_in | array | Value is not in the provided list. | ["spam", "bounced"] |
exists | none | Field exists and is not null. No value needed. | - |
not_exists | none | Field does not exist or is null. No value needed. | - |
between | array | Value is between two bounds (inclusive). | [100, 500] |
Type constraints: gt, gte, lt, lte, and between only work with numeric values. contains and not_contains only work with strings. Using an operator with the wrong value type returns no matches.
Filter group structure
A filter group wraps one or more filters with a logic operator.
{
"logic": "and",
"filters": [
{ "type": "contact_field", "field": "lifecycleStage", "operator": "eq", "value": "customer" },
{ "type": "metadata", "field": "region", "operator": "eq", "value": "us-west" }
]
}| Field | Type | Required | Description |
|---|---|---|---|
logic | string | Yes | and or or. Determines how filters are combined. |
filters | array | Yes | Array of filters or nested filter groups. |
Nested logic
Filter groups can contain other filter groups for complex AND/OR logic:
{
"logic": "or",
"filters": [
{
"logic": "and",
"filters": [
{ "type": "contact_field", "field": "lifecycleStage", "operator": "eq", "value": "lead" },
{
"type": "behavioral",
"field": "event_count",
"operator": "gte",
"value": 0,
"behavioralWindow": {
"eventName": "email_opened",
"windowDays": 30,
"countOperator": "gte",
"countValue": 3
}
}
]
},
{
"logic": "and",
"filters": [
{ "type": "contact_field", "field": "lifecycleStage", "operator": "eq", "value": "customer" },
{ "type": "metadata", "field": "plan", "operator": "eq", "value": "enterprise" }
]
}
]
}This matches contacts who are either (leads with 3+ email opens in the last 30 days) OR (enterprise customers). Nesting depth is unlimited.
Related
- Contacts - manage the contacts that segments filter on
- Analytics - check segment membership with
segment-check - Experimentation - target an experiment at a segment with
segmentId
MCP tools
The segment tools are in the opt-in journeys toolset (see MCP server):
| Tool | Parameters |
|---|---|
create_segment | name, filter_group (logic, flat filters[]; no nesting and no behavioral_window, use update_segment for those) |
get_segment | segment_id |
update_segment | segment_id, name, filter_group (supports behavioral_window: event_name, window_days, count_operator, count_value) |
archive_segment | segment_id |
compute_segment | segment_id |
check_membership | segment_id, contact_email |
segment_members | segment_id, limit (1-500), offset |
contact_segments | contact_id |
There is no list tool; read the molted://segments resource instead.