GenieOS · Audience

Segment on your own columns.

Your users are already in a database you own. Connect it, and audience stops being a copy you sync and becomes a query you run: lists built from live production attributes, ICPs your agents can read in full, and every send addressed to people as they are right now, not as they were at the last import.

agent.tsts
// a list from a predicate on your own schemaconst dormant = await genieos.create_list({  name: "Imported, never connected",  predicate: "joined_this_month AND source_count = 0",}); // or curate one by handawait genieos.add_list_members({  list: dormant.id,  members: ["[email protected]", "[email protected]"],});
The surface
  • create_listA list from a predicate on your own columns, or an empty one to curate.
  • add_list_membersPut specific contacts on a list, by hand or from your own code.
  • list_listsEvery list, with counts.
  • read_listOne list in full, members and their provenance included.
  • list_icpsThe ICPs the strategy targets.
  • read_icpOne ICP in full, so an agent writes for a person rather than a segment.
AudienceFueld

GENIE · AUDIENCE

Everyone you can reach.

Contacts, lists, and segments in one place. Switch between them up top — every send, sequence, and campaign draws from here.

ContactsListsSegmentsInbox65 CONTACTSNew contactConnect
CONTACTCOMPANYSOURCESSENTOPENSLAST SEEN
Sana Patel[email protected]CSV0013m ago
Omar Diaz[email protected]CSV0013m ago
Femi Sato[email protected]CSV0013m ago
Xavi Cobb[email protected]CSV0013m ago
Jack Ortiz[email protected]CSV0013m ago
Erin Lane[email protected]CSV0013m ago

BRING AN AUDIENCE IN

ApolloProspect and import people, in chat
SegmentSOONSync audiences from your CDP
HubSpotSOONImport lists from your CRM
CSV importUpload a file — columns mapped for you
Auto-captureForms and replies land here on their own
The connect menu on the audience surface: bring an audience in, or connect a production database.

Production databases

  • Supabase
  • Firestore

No reverse ETL.

The standard architecture is a pipeline: warehouse to sync tool to marketing platform, a copy of your users maintained in someone else's schema, always a little stale. GenieOS skips it. Connect a production database from the audience surface and a segment is a predicate evaluated against your live columns. Nothing is exported, and nothing waits for a sync window.

The same connection powers measurement, and it goes further than segmentation. Bind a campaign to a column in your own database, users.activated_at say, and movement in that column is attributed back to the campaign. Success is your number in your schema, not an open rate in ours, and it is the number the loop reads before it derives the next campaign.

IN-APP · JUDGEThe count, before the send. The API answers slowly; this answers at a glance.
Audience · New segmentFueld

← All segments

New audience

Describe who you want to reach — Genie builds the audience from your own profiles (traits, engagement, lists). Save it, preview the count, and activate it to email. No database or CSV needed.

START FROM AN EXAMPLE

People who opened in the last 30 days but never clickedHighly engaged contacts — engagement score over 40Lapsing: no activity in 60 daysPro-plan customers in the UK, excluding anyone unsubscribed

DESCRIBE YOUR AUDIENCE

Highly engaged contacts — engagement score over 40
Build with Genie

Forty people, or forty thousand?

A predicate reads the same either way. joined_this_month AND source_count = 0 could be a quiet week or your entire import cohort, and the difference decides whether the send is a gesture or an incident. Over the API you would answer it by paging through read_list and counting.

The app answers it before you finish typing. The audience composer previews the live count as the predicate narrows, which makes it the one place worth pausing before an agent sends to a list it just built. Judgement is cheap here and expensive after the send.

read_list.jsonjson
{  "list": "Imported, never connected",  "count": 1184,  "members": [    {      "email": "[email protected]",      "sources": ["paste"],      "joined": "2026-07-12"    },    {      "email": "[email protected]",      "sources": ["send", "manual"],      "joined": "2026-07-19"    }  ]} // every contact carries the route it arrived by

Every contact knows how it got here.

Contacts carry their history in a sources column: pasted in, arrived through a send, added by hand. It reads as a small detail until the day you need it, at which point it is the audit trail: why is this person on this list, and who put them there.

It is the same idea as derived, one level down. Artefacts know the campaign they came from; contacts know the route they arrived by. The whole system runs on lineage you can query, because automation you can read is the only kind worth trusting at scale.

When this happens
  • WhenA feature flag reaches 100%

    ThenAn announcement to the cohort that did not have it

    • create_list
    • compose_template
  • WhenA Sentry issue crosses your user threshold

    ThenA transactional email to exactly the affected users, status and ETA

    • create_list
    • send_transactional

Your database is the audience.