Signals · Genosyn Docs

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 Revenue → 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 with the same control a Routine uses — pick a cadence, see the next few times it will run, and drop to a custom cron expression if you need one. 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.

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:

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:

  1. The row count is small and the rows are the ones you meant.
  2. The dedupe column is present, non-null, and identical for the same account across two runs.
  3. 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

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.

AI Employee administration

An AI Employee with Revenue Write access can create and edit Signal definitions and actions, test them, inspect Signal event history, enable or disable them, and archive or restore them. Testing a SQL Signal also requires a Grant to its database Connection; a Revenue grant alone never exposes an unrelated Connection.

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.