What this changes for a partner team
A partner-recruitment audience for Anthropic's Claude Partner Network, built in Clay from two public partner directories (Snowflake Services Partners, Americas, and Databricks C&SI Partners), segmented, and wired to a sender. What a team gets from it: an audience whose enrichment bill tracks the accounts that qualified, a list that can be defended row by row because every removed account keeps its reason, segments that change what the rep says rather than just how the list is sorted, rows that move to the right table on their own when the research changes, and a sender that refuses to send unless a person has approved the row and the server-side caps allow it.
The thesis
Anthropic sells through Snowflake and Databricks as much as it sells direct, and the consulting and systems-integration firms that deploy on those platforms are the recruitable layer. Not the Big Four. The small and mid-size shops that already have a Snowflake or Databricks bench and no model vendor they are married to.
Qualifying on free signals
The directories go in deduped, with raw_source_value kept on every row. The free gates run first, in order: identifiable non-US entities out, then Big Four, global SIs and platform vendors out, then a data-quality gate. Every disqualified row lands in its own table with a reason. Only then does paid enrichment run, on the survivors: domain resolution, HQ verification, the headcount gate. Enrichment cost tracks qualified accounts, not list size.
Scoring
Every point in fit_score_0_7 traces to a thesis claim: Snowflake tier, multi-homing across both directories, Americas region confirmed, source name unaltered by the cleaner, and a Databricks AI specialization badge as a bonus. The score bands the qualified rows into A, B and C, and each band can be explained from the row.
The finding that changed the model
The Databricks AI specialization badge is a large-SI credential. Filtered to C&SI Partner plus AI, the firms that hold it are Accenture, Avanade, Capgemini, Cognizant, DXC, EPAM, EY, Infosys and firms of that size, with exceptions that plausibly sit in the target band: Aimpoint Digital and Koantek.
So the badge cannot be a gate. Used as a gate it selects almost exactly the firms the ICP excludes. It works as a tie-breaking bonus point. Same pattern as PRM-platform detection on Xtrm's own partner-program audience: a bonus signal, never the Keep or Flag gate.
The test for a segment
A filter tells you a row is different. A segment tells you the rep is different. The only question asked of a candidate cut: does the first sentence of the email change, does the proof point change, does the recipient change. If most of that stays the same, it is a filter, and it goes. Every firmographic cut (tier, cert density, headcount band, region, multi-homing) left the conversation unchanged. What changes it is what stands between the firm and putting Claude in front of a client. Three answers, mutually exclusive.
- S1 Cortex Native. They build on Snowflake Cortex, where Claude is already a hosted model. Adopting it is a model string in a SQL function they already call. First touch: a one-page benchmark offer.
- S2 Committed Elsewhere. They name OpenAI, Azure OpenAI, Bedrock, Vertex, Gemini or Databricks and show no Cortex. A displacement argument with a practice lead who has an opinion. First touch: a teardown of one of their published case studies.
- S3 Practice on Paper. They market an AI service line, name no model vendor, and mostly show no dated shipped work in the last year. The ask is "build the practice and we fund the first one", a founder's decision. First touch: a funded pilot scoped to one named client of theirs.
- Unassigned. Already name Claude and nothing else. Routed to the partner team, not to a sequence.
| Rejected cut | Why it failed the test |
| Snowflake tier | Nobody opens differently to a Premier shop than to an Elite shop of the same size. |
| Cert density | A headcount proxy, shown the week before. |
| Headcount bands | Size changes who signs, not what you say. |
| Region | Decides who sends, not what the email says. |
| Multi-homed | Shifts the proof point only. One of the three tests. |
| Vertical | Same ask, same channel, different compliance paragraph. |
| Public sector | Contradicts a gate set the week before on incentive-payment restrictions. |
| Founder-led vs alliances-managed | Real, and it cuts across every segment. Lives inside each playbook as the channel line. |
The routing column
// Clay formula column: segment. Reads one upstream Claygent column, model_vendors.
model_vendors contains "Cortex" -> S1 Cortex Native
else contains OpenAI | Azure OpenAI | Bedrock |
Vertex | Gemini | Databricks -> S2 Committed Elsewhere
else contains "none found" -> S3 Practice on Paper
else (names Anthropic Claude only) -> UNASSIGNED, route to partner team
One destination table per segment, each fed by its own Send table data action whose run condition is this column. Rows that change segment move tables on their own. Cortex wins ties on purpose: a partner already on Cortex is the shortest distance to revenue, so the competing vendor becomes the objection the rep handles rather than the bucket the account sits in.
The honest part
S3 came up short of the plan. No vendor evidence and no shipped work turn out to be the same thinness measured from two directions, so most of S3 fails a later disqualifier. I reported the accounts to work now, the ones to track, and the shortfall against the plan.
The send caps, in code
// Apps Script endpoint. Runs on the server, not in Clay's run condition. A mis-set filter cannot get past it.
// CONFIG holds MAX_TOUCHES, COOLDOWN_DAYS, DAILY_SEND_CAP, TOTAL_SEND_CAP and MODE. MODE starts as 'DRY_RUN'. Values omitted here.
const idem = email.toLowerCase() + '#' + touch; // Clay columns re-run. This stops a duplicate send.
if (prior && prior.result === 'SENT') return json_({ ok: true, result: 'ALREADY_SENT' });
function blocked_(p, touch, log, email) {
if (touch > CONFIG.MAX_TOUCHES) return 'touch out of range';
if (truthy_(p.stop)) return 'stop flag set';
if (truthy_(p.replied) && touch > 1) return 'already replied, exits sequence';
if (!String(p.send_guard).startsWith('CLEAR')) return 'copy gate: ' + p.send_guard;
if (days < CONFIG.COOLDOWN_DAYS) return 'cooldown, ' + left + ' days left';
if (sentToday >= CONFIG.DAILY_SEND_CAP) return 'daily cap reached';
if (sentTotal >= CONFIG.TOTAL_SEND_CAP) return 'total cap reached';
return null;
}
Clay's HTTP API column POSTs each row here. The script takes a lock, checks a shared secret, runs these checks, drafts or sends through Gmail, and returns the result so Clay writes status back into the row. A scheduled trigger walks replies and POSTs stop, bounce or pending back to a Clay webhook. The log sheet is append-only. Mode stays DRY_RUN until the first drafts have been read by a person.
Where the human sits
cortex_evidence_check, Claygent: CONFIRMED, LIKELY, NOT_FOUND or UNCERTAIN with a verbatim quote and URL. The prompt says a false CONFIRMED is much worse than a NOT_FOUND, because a human is going to state this back to the company.
cortex_verified_by_human, a checkbox, ticked only after I clicked the URL myself. Nothing downstream runs without it.
email_draft, a drafting model with no web access, run condition on the checkbox. The columns it may read are inserted into the prompt by name. final_grade, the ARR estimate and the exclusivity flag are excluded by name.
banned_phrase_check, a formula, not AI: deterministic and free. Blocks partner-program language, pricing, "on behalf of Anthropic", "book a demo", and any em or en dash.
send_approved, a second checkbox per row. The endpoint refuses anything that arrives without CLEAR.
| Guardrail | Where it lives |
| Touch limit and cooldown | Apps Script, checked against the log sheet |
| Daily and total send caps | Apps Script, counted from SENT rows |
| Idempotency key | email#touch |
| Copy gate | Clay formula column, CLEAR or BLOCKED |
| Opt-out | Reply loop writes stop=TRUE back to Clay |
What broke
- The first Databricks pull was unfiltered and brought in ISVs and cloud vendors. Their filter checkboxes do nothing until you click Apply Selected Filters. The second pass applied C&SI at the source, so the wrong-type companies never entered the table.
- Databricks renders company names as logo images, so a share of the rows came back as image filenames. Normalization recovered most;
raw_source_value is kept on every row for audit.
- My noise-word stripper removed the word "digital". Aimpoint Digital, LP became "Aimpoint , LP", on the highest-scoring row. Caught late, fixed.
- Neither directory has a country filter. US-only was approximated by excluding unambiguous non-US legal suffixes and geography words. Remaining rows are marked
PASS (presumed US, verify HQ).
- Clay's JSON Schema output mode failed silently on one column. Every AI column was rebuilt with Fields output.
- An ARR-potential gate turned out to be headcount times a constant, so it had quietly narrowed the headcount band. Removed. The test that caught it: can the signal vary independently of the gates above it.
- The dormancy check fails any firm whose case study has no publication date. Small consultancies do not date theirs, so it partly measures publishing habits, which correlates with size again. Not fixed; flagged on the column.
- The Cortex-first tie break is the load-bearing assumption and it is untested. If a partner running both Cortex and OpenAI behaves like an OpenAI shop, S1 shrinks and S2 grows.
The request
POST https://api.clay.com/public/v0/tables/query
clay-api-key: <server side, never in this page>
{ "query": { "tables": [ { "id": "t_0tkgaa2wuHGApf5CBas" } ], "field_mode": "names" },
"limit": PAGE_SIZE } // paginate with the returned cursor; keep the field list
One request per page, per table. The key stays on the machine that runs scripts/clay_pull.py. The page shows the source and the pull date on the Dash card.