Revenue

Signals

A Signal is a saved query over your own product database plus a rule for what to do with the rows that come back. Your database already knows who is about to churn, who just hit a seat limit, and whose trial ends on Thursday — this is how that turns into a contact, a deal, a sequence enrolment, or an AI employee doing something about it. Find it under Revenue → Signals.

Creating one

  1. Revenue → Signals → New signal. Name it after the condition, not the action — Trial ending in 3 days reads better on a timeline than Enrol trial users.
  2. Pick the Connection to run against: any Postgres, MySQL or ClickHouse connection you have added under Settings → Integrations, the same ones Explore uses.
  3. Write the query, and map its columns (below).
  4. Press Test and look at the rows before you go any further.
  5. Set the schedule — a standard 5-field cron, validated against the scheduler that will run it, exactly as on a Routine. Hourly is a sane starting point.
  6. Choose the action, then flip Enabled on.

Writing the query

The query should return one row per thing you want to act on, and each row should carry the columns the action needs. Keep it narrow: a signal that matches 500 accounts on a single tick is a misconfigured query, not an alert.

sql
SELECT
  a.id            AS account_id,
  u.email         AS email,
  a.domain        AS domain,
  a.plan_mrr_cents AS amount
FROM accounts a
JOIN users u ON u.id = a.owner_user_id
WHERE a.trial_ends_at BETWEEN now() AND now() + interval '3 days'
  AND a.converted_at IS NULL
ORDER BY a.plan_mrr_cents DESC

Then map the columns:

Dedupe key
The column identifying the subject of the row — an account id, a subscription id, an email. Required in practice; see below.
Email
Used to resolve an existing Contact, or create one. This is one of the few paths that may create a contact, because a signal firing is an explicit act rather than inbox noise.
Domain
Used to resolve the Customer account. acme.com, https://www.acme.com/pricing and @acme.com all normalize to the same account.
Amount
A money amount in minor units (cents), used when the action opens a deal.

Queries run through Explore's executor and inherit its envelope: a 30-second timeout and a hard row ceiling. A signal looks at at most 500 rows per tick; the overflow is not lost, because the rows that did fire have their dedupe keys stored and the next tick picks up where it left off. A signal with an ORDER BY therefore drains in priority order — which is why the example above sorts by revenue.

The dedupe column is not optional

A signal re-runs its query on every tick. Without a way to tell "this row already fired" from "this row is new", the same account alerts you every hour, forever — and the real damage is what happens next: somebody mutes the signal, and then it never fires for the row that actually mattered.

Name a dedupe column and the guarantee is a database constraint, not a hope. Genosyn stores one event per (signal, dedupe key) pair with a unique index, so an account fires once per condition even when two replicas evaluate the same signal in the same second — the loser hits the constraint and correctly does nothing.

Pick something that does not change: an account id, a subscription id, a user id. If you want a condition to be able to fire twice for the same account — a trial that restarts, a limit hit in two different months — put the period into the key in SQL, for example account_id || ':' || to_char(now(), 'YYYY-MM'). That is a deliberate choice you can read months later.

Test before you enable

Test is a dry run: it executes the query and shows you the first rows and their column names, and it writes nothing. No events, no contacts, no deals, no notifications, no enrolments. Use it until three things are true:

  • The row count is small and the rows are the ones you meant.
  • The dedupe column is present, non-null, and identical for the same account across two runs.
  • The email and domain columns hold what the action will need.

Test runs are written to the audit log even though they change nothing, because the read itself — arbitrary SQL against a connected production database — is the thing worth having a record of.

What happens when a row fires

Log an activity
Writes a signal row onto the contact's timeline. The safe default and the right setting while you are still tuning the query.
Notify
A bell notification and a web push to the company's owners and admins.
Create a deal
Opens a Deal in the first stage of the board, using the amount column for its value and attributing its source to the signal.
Enrol in a sequence
Adds the contact to a Sequence you pick. Every enrolment gate still applies — suppressed, do-not-contact and already-enrolled people are refused, and the event records that it was refused rather than pretending it worked.
Hand to an AI employee
Wakes the employee you name with the whole result row and an instruction, and lets it decide what to do — research the account, draft an email, open a deal, escalate. It runs with its full Soul and Skills, and needs a revenue grant at the level its actions require.

Events, and failures you can see

Every firing is a Signal event carrying the full result row, listed under Revenue → Signals → Events with a status: new, actioned, ignored, or failed. ignored is not an error — an enrolment refused because the person unsubscribed is the system working exactly as designed. failed means somebody has to fix something.

A signal whose query throws keeps its schedule and shows the error on its own row rather than disabling itself, because a signal that quietly switched itself off would be discovered weeks later, by which time the rows it should have fired on have moved on. One broken signal never stops the others in the same pass.

Connect with a least-privileged role

Create a dedicated role for Genosyn, grant it SELECT and nothing else, scope it to the tables signals need, and point it at a read replica if you have one. Do this before you connect, not after — the same credential is what any AI employee granted that Connection reaches through, and a signal is scheduled, so a destructive query does not need anybody to be watching.

  • Explore — the query executor and the connections signals run against.
  • Sequences — where an enrol action sends people.
  • Deliverability — read this before any signal starts sending mail.