Documentation

Audiences and segmentation

Define reusable contact segments using DynamicQuery filters, custom audiences, and lookalike rules.

An audience is a saved query against contacts (or any CRM record) that materialises a list of recipients at the time a campaign launches. Audiences let you write the filter once — "VIP customers who opened an email in the last 30 days" — and reuse it across campaigns, ads, automations, and reports.

Audience CRUD

POST/crm/audiences/segmentsJWT
GET/crm/audiences/segmentsJWT
GET/crm/audiences/segments/:idJWT
PUT/crm/audiences/segments/:idJWT
DELETE/crm/audiences/segments/:idJWT

A segment body:

{
  "name": "VIP recent openers",
  "description": "VIP-tagged contacts who opened a marketing email in the last 30 days",
  "filter": {
    "datatype": "contact",
    "where": {
      "$and": [
        { "tags": "vip" },
        { "lastEmailOpenedAt": { "$gte": "2026-03-25T00:00:00Z" } }
      ]
    }
  },
  "type": "dynamic"
}

The filter object uses the DynamicQuery syntax — the same MongoDB-style operators the rest of AppEngine accepts. type is dynamic (re-evaluated at use) or static (a frozen list of contact IDs).

Custom audiences

POST/crm/audiences/customJWT
GET/crm/audiences/customJWT
GET/crm/audiences/custom-typesJWT

Custom audiences are uploaded lists — CSV imports, lookalike seeds, ad-platform-synced audiences. custom-types returns the categories the platform supports (file upload, customer match, lookalike, retargeting pixel).

Insights and reach estimation

GET/crm/audiences/segments/:id/insightsJWT
POST/crm/audiences/estimate-reachJWT
GET/crm/audiences/segments/:id1/overlap/:id2JWT

insights returns demographic and engagement breakdowns of the current segment population. estimate-reach accepts a filter and returns a count without saving, which is what the audience builder uses for "this matches 1,247 contacts" feedback as the user edits.

const reach = await fetch('/api/crm/audiences/estimate-reach', {
  method: 'POST',
  headers: { orgid: ORG_ID, Authorization: `Bearer ${jwt}` },
  body: JSON.stringify({
    filter: {
      datatype: 'contact',
      where: { country: 'US', tags: { $in: ['lead', 'mql'] } },
    },
  }),
}).then(r => r.json());
// { count: 1247, sampleProfile: { ... } }

overlap between two segments returns how many contacts appear in both — useful for knowing whether two campaigns will hit the same people.

Targeting helpers

GET/crm/audiences/interestsJWT
GET/crm/audiences/locationsJWT
GET/crm/audiences/targeting-optionsJWT

These return platform-specific taxonomies (Facebook interests, geo regions, ad platform targeting trees). The audience builder UI consumes them so users pick from real ad-platform values rather than free-typing strings that won't sync.

Using audiences

Audiences plug into:

  • Marketing campaigns — set audienceId (see Marketing campaigns).
  • Automations — trigger a flow when a contact enters or leaves an audience.
  • Ads — sync to Facebook, Google, TikTok, LinkedIn ad platforms via the ads module.
  • Reports — slice analytics by audience membership.

Dynamic audiences are evaluated lazily. A campaign launched at noon sees the audience as it exists at noon. If you add a contact at 12:01, they won't be in that send — but they will be in the next launch.

Performance

Audience evaluation hits the same indexes as the data layer. If a filter scans tens of thousands of contacts and runs slowly, add an index on the filtered fields via POST /collection/index. The platform doesn't pre-materialise dynamic audiences — every read goes through MongoDB.