Skip to main content
Speed-to-lead is the whole game: a form submission that nobody hears about for four hours is worth a fraction of one somebody calls back in ten minutes. An arrival notification closes that gap. You name a dataset and a list of addresses, and Erdo emails those people within seconds of a new row arriving. You declare who and where. Erdo owns everything underneath — the automation, its trigger, the watermark that separates new rows from old ones, and the message itself. There is no script to write and nothing to keep in sync.

Why it attaches to a dataset

Notifications hang off the dataset, not off the pipeline that fills it, and that distinction matters more than it first appears. A real development runs many lead-capture pipelines — one per landing page, per language, per variant. One customer has 25 of them, all writing a single leads dataset. A notification attached to a pipeline covers that pipeline alone, so the day someone launches landing page 26, its leads stop reaching anyone and nothing looks broken. Attaching to the dataset covers every writer, including the ones that don’t exist yet.

Setting it up

PUT /v1/dataset-notifications takes the dataset slug and the full recipient list. The list replaces what was there — send the complete set, not a delta.
GET /v1/dataset-notifications?dataset=<slug> reads the current state back — recipients, whether it’s on, and the automation implementing it. That state is read from the automation itself, so there is no second copy of the setting to drift out of sync with reality. Passing an empty recipient list turns notifications off.
Turning notifications on never delivers a backlog. Where the dataset currently stands is recorded at the moment you configure the notification, so only rows arriving afterwards are announced — including the very first one. Turning notifications off removes the automation rather than pausing it, for the same reason: switching back on months later must not dump everything that accumulated in between into someone’s inbox.

What arrives

Ordinary arrival sends one email per row, so each lead is its own actionable message: who they are and how to reach them first, then what they told the form, then the campaign that brought them. Tracking identifiers — click ids, page URLs, the flags the pipeline stamps on the way in — are join keys for machines and appear nowhere in it. The language they submitted in does appear, because it decides who on the desk can take the call. A burst collapses into a single digest instead of flooding the inbox: either more rows than one run reads, or more messages than one run may send. Every message is tagged with context recording the dataset and which row it concerned — {"kind": "lead_alert", "lead_email": "ana@example.com"} — so your own product can later show “we alerted the team about this lead” on the lead itself. This is the only way that link can be made: the recipient is the sales desk, so nothing about the address says which lead the message was about.

The same lead arriving twice

A lead dataset is not a log. Capture pipelines upsert on the address, so a landing page that asks for an email first and the rest a minute later writes the same lead twice — one row in the dataset, two writes. Announcing that as two leads is wrong, and it is not a rare shape: on one development a quarter of all leads are written more than once, far enough apart that no amount of batching would hide it. The write settles it. Every row records the moment the dataset created it, in a column named erdo_created_at, and a second write to the same row leaves that moment alone however far the arrival column moves. A notification keeps its position in that column as well as in the arrival column, so a row created no later than the point it has already reached is a lead it has announced — including one that arrived in a burst too large for a single run to read. On top of that it also remembers which leads it has announced by address, and what was known about each at the time, which is what covers rows written before erdo_created_at existed. Either way a row it recognises is not announced again, and the run result counts it. Two leads that both arrived without an address are still two leads — an absent address identifies nobody, so neither is suppressed.

Cost

Nothing per run. The automation is a deterministic script fired by an event trigger on the dataset — no LLM, and no polling loop burning tokens to ask whether anything changed. It runs when a row lands and is otherwise idle.
Don’t build this with a recurring agent or a polling heartbeat. A five-minute heartbeat doing this job once cost a customer roughly $81 in a single week to notice one or two leads a day — while also being up to five minutes slower than the event trigger.

Skipping rows you describe

Not every new row deserves an email. Public lead forms attract junk — typo’d addresses that can never receive a reply, gibberish, staff tests — and some desks simply don’t want alerts for rows missing what they need to act. Which rows those are is your call, not the platform’s, so you describe them:
or
Each new row is judged against your description before its email, and a row that matches sends no alert. The judgement is instructed to be conservative: when unsure, the row does not match and the alert is sent. Where the row carries an email address, the judgement also receives a piece of evidence: a DNS lookup of the address’s domain, stated only when it returns a definitive this domain does not exist. The fact informs your criterion rather than overruling it — “spam” will weigh a non-existent domain heavily, while “anyone who did not leave a phone number” will rightly ignore it. A skipped row is never silently dropped: the automation’s run result records each one with its address, name, and the reason, and the run summary counts them. The row itself is untouched — it still lands in the dataset and still appears in every count; only the alert is suppressed. The check fails open. If the judgement errors, the email sends — a broken skip check must never become a broken alert channel. The gate applies only to per-row alerts; a bulk import’s digest is already one bounded message and is delivered as before. An empty skip_when means every new row is announced.

Which column marks arrival

Erdo detects the column recording when a row arrived (submitted_at, created_at, received_at, and similar) by probing the dataset’s real columns when you configure the notification. If your dataset names it something unconventional, pass timestamp_column explicitly. Detection happens at configuration time on purpose: a dataset with no usable column is an immediate error you can fix, rather than an automation that looks perfectly healthy and quietly never notifies anyone.

The recipient list is also the permission

Sending email is a consequential action, so an automation cannot do it unless something authorises it — and an unattended run has nobody to ask, which is why a script that emails on a schedule needs a standing approval granted in advance. A notification does not need one, because the recipient list is the authorisation. Saving a notification records permission for that automation to email exactly the addresses you listed. Remove an address and the permission goes with it; turn notifications off and it is withdrawn entirely. The automation cannot reach an inbox you did not name, even if something goes wrong with the message it composes. This is the difference between a notification and a hand-written script that sends mail. The script names its recipients in code nobody reviewed as a decision about who may be written to, so it is refused until somebody grants it. A notification is that decision, made when you saved it.

Limits

At most 10 recipients per dataset — each recipient’s email is one action invocation, and one run may make ten, so a longer list could never be delivered. Sends also count toward your organization’s daily distinct-recipient limit of 50.

Sent email

Read back everything that went out, including these alerts.

Automations

The run history of the automation behind a notification.