Clay
Enrichment, segmentation and routing
A spreadsheet that can call APIs. Each column runs an enrichment, an AI prompt, a formula or a push to another system, on every row.
Clay is where most of my work actually happens. It is the layer between “here is a list of companies” and “here is a person, at a company that matches, with the five facts the message needs, verified, and allowed to be contacted.”
How I use it
A hub, then satellites. One company-level table does the expensive shared work: find the domain, scrape the site, classify the business, score fit. That table then routes qualifying rows into per-segment people tables, and each of those pushes into its own campaign. The alternative, one giant table doing everything, re-runs company enrichment once per person and costs several times as much for the same answer.
Waterfalls, not single providers. No email provider covers a whole list. So a
column runs provider one; a formula extracts the result; provider two is gated to
fire only where the first came back empty; and so on, ending in a coalesce that
takes the first valid answer. Ordering matters, and so does the gate: the trap is
that a failed lookup often returns the string "NO" rather than an empty cell,
which is truthy, so a naive if empty gate never advances past attempt one and
the waterfall silently stops.
Layout as documentation. Columns run left to right in true dependency order, each action immediately followed by the formula that extracts its result, each stage in a named group. A table whose column order matches its run order explains itself to whoever opens it next. One that does not is a debugging tax forever.
Gates before anything irreversible. The push column is last, and it only fires when the record has passed every check: a verified address, the variables its campaign copy references, the segment value that routes it there, and the prior-contact lookup. Those checks live in the table as columns the gate reads. That placement is the whole point, and I learned it the hard way, because a check written by a script that happens to run first is not a check at all once the table runs on its own schedule.
What I have learned the hard way
Auto-run only fires on new or changed rows. Switching it on for a batch of rows that already exist does nothing, and a no-op looks exactly like a job that is early. I once reported a run as in progress for over an hour when it had dispatched nothing.
A blank cell is four different states. Never ran, ran and errored, ran and found nothing, or correctly skipped by its condition. They demand opposite responses, and an exported CSV strips the run status that tells them apart. I wrote a whole post about this.
Cells have a size limit, so a formula that stores a whole scraped page gets truncated and silently breaks the gate reading it. Extract a focused window around the fact you need, not the first N characters, which are navigation.
Every column has to earn its place. Duplicating a table for a new segment carries across columns the new copy never reads, and dead columns are not free: they blur cost, hide what is real while debugging, and occasionally get triggered by accident on rows nobody meant to touch.
Resources
This is one part of a stack. The rest is on the tools page, and what I build with it is on work.