The Data Quality Playbook
Every GTM play you run sits on top of your data. If the data is dirty, the signal is noise and your automation just amplifies the mistake faster. This is a plug-and-play workflow — Apollo to acquire, Google Sheets to hold, Claude to clean — that gives you a self-correcting contact and company database you can stand up today. The prompts and the steps are below. Copy them and run.
Data quality is the least glamorous part of GTM engineering and the one that decides whether everything downstream actually works. A scoring model trained on stale titles scores noise. A routing rule keyed on a misspelled domain routes to nobody. A sequence personalized from the wrong company name does worse than no sequence at all — it tells the buyer you did not look. The expensive tools sitting on top of your stack are only ever as good as the rows underneath them.
So before any of the interesting plays — attribution, ABM, intent — there is this one. It is unglamorous, and it compounds. Clean data never announces itself; it just quietly makes every other number true. Here is the call on what to buy, what to build, and the exact workflow I run.
What to buy, what to build
Apollo
Companies, contacts, and firmographics from $49–79/mo. Pulling raw records is a solved problem — do not build a scraper to re-solve it.
Google Sheets
A sheet is a perfectly good CRM for one operator: queryable, scriptable, shareable. Graduate to a real CRM the day a second person needs it.
Claude
The cleaning, the normalization, the judgment — this is where a GTM engineer earns the title. You build this part with prompts, not code.
Four stages, run as a waterfall: Acquire raw records, Normalize them to one shape, Dedupe against what you already have, then Validate & enrich so every row is trustworthy and labeled. Each stage hands clean output to the next.
Stage 1 — Acquire
Apollo to Sheets. The goal of this stage is volume of raw records, not clean ones. Cleaning comes next.
- In Apollo, build a saved search for your ICP. For GTM Ops work that is growth-stage companies, Series A–D, with an open RevOps or GTM-engineering req or a CRO/CMO who started in the last 90 days.
- Export the people view with first name, last name, title, company, domain, LinkedIn URL and the firmographic fields. Apollo Basic exports manually; the API on Professional lets you schedule it.
- Paste the export into a Sheet tab named raw. Never hand-edit raw. Every later stage reads from it and writes to a clean tab, so you can always re-run from source without losing work.
Stage 2 — Normalize
Raw exports are inconsistent by nature. “VP, Revenue Operations,” “VP RevOps,” and “V.P. Rev Ops” are the same job. “Acme, Inc.” and “Acme Incorporated” are the same company. Normalize to one shape first, or every match you attempt later quietly fails. Paste your raw rows into this prompt; write the output to a clean tab.
You are a GTM data normalization engine. I will paste rows from a raw contact export, tab-separated, with these columns: first_name last_name raw_title raw_company domain linkedin_url For each input row, return exactly one row, tab-separated, in the SAME order, with these columns: first_name last_name canonical_title segment company_clean domain seniority Rules: - canonical_title: the standard form of the role (e.g. "VP, Revenue Operations"). Expand abbreviations, fix casing, drop punctuation noise. - segment: choose exactly one of: GTM Ops Leaders | Sales Leadership | Marketing Leadership | Growth & Marketplace | Client Success | Finance & Founder. Map by what the role owns, not the literal words. - company_clean: the common company name. Remove legal suffixes (Inc, LLC, Ltd) and trailing punctuation. - domain: lowercase root domain only (acme.com, never www.acme.com or a path). - seniority: one of C-level | VP | Head/Director | IC. Return only the data rows. No header, no commentary, no code block. Preserve input order exactly. Rows: [PASTE YOUR RAW ROWS HERE]
Stage 3 — Dedupe
Dedupe on a stable key. For people that is lowercase email when you have it, otherwise lowercase first + last + domain. For companies it is the root domain. Build the key in Sheets, flag exact collisions with conditional formatting, then let Claude resolve the near-matches a formula will always miss.
=LOWER(TRIM(IF(E2<>"", E2, A2 & B2 & F2))) # E = email, A = first_name, B = last_name, F = domain # Then flag duplicates: Format -> Conditional formatting -> Custom formula: # =COUNTIF($G$2:$G, $G2) > 1
The formula catches exact duplicates. The typos, nicknames, and subsidiaries it cannot. Hand those to Claude.
Here are contact rows that share no exact key but may be the same person or company (typos, nicknames, subsidiaries, formatting drift). Group any rows that point to the same real-world entity. For each group tell me which row to KEEP (most complete, most senior title, freshest source) and which to DROP. Return, tab-separated, one line per dropped row: keep_row_id drop_row_id reason If a row has no duplicate, omit it. No commentary. Rows: [PASTE SUSPECTED NEAR-DUPLICATES, INCLUDING A row_id COLUMN]
Stage 4 — Validate & enrich
The last pass turns clean rows into trustworthy, labeled rows. It flags anything that looks wrong — a “CFO” on a personal Gmail address, a title that does not match its company — confirms the segment, and writes the one-line “why now” your outreach will lean on. Crucially, it is told to invent nothing: the why-now is grounded only in the trigger you already captured.
You are validating a cleaned GTM contact list before it drives outreach. For each row, return one row, tab-separated: row_id valid flag_reason segment_confirmed why_now - valid: yes/no. Say no if the row is internally inconsistent: a free-mail domain (gmail, outlook) on a senior title, a title that contradicts the company, or obvious test data. - flag_reason: one short phrase, or blank if valid. - segment_confirmed: re-affirm or correct the segment, from: GTM Ops Leaders | Sales Leadership | Marketing Leadership | Growth & Marketplace | Client Success | Finance & Founder. - why_now: one sentence a human could actually send, grounded ONLY in the trigger fields provided. Invent nothing. If the trigger is weak or stale, write "no strong reason". Columns provided: row_id first_name last_name canonical_title segment company_clean domain trigger_type trigger_date Rows: [PASTE CLEAN ROWS]
Make it run on its own
Data quality is not a one-time cleanup, it is a standing process. Pick the cadence that matches your budget.
- Manual, free. Once a week, re-export from Apollo into raw, run the three prompts in order, paste the results into clean. Twenty minutes, and your database is current.
- Scheduled, Apollo Professional + Apps Script. A Google Apps Script time-trigger pulls the Apollo API into raw nightly; a second function sends the three prompts to the Claude API and writes clean. Now the database refreshes itself while you sleep.
This clean, labeled table is exactly what the Prospecting Engine reads. The trigger_type and trigger_date you validate here are what its priority logic ranks on. Clean data in, a ranked, human-approved queue out.
What good looks like
- Every contact maps to exactly one segment, and that segment matches what the role actually owns.
- Company names and domains are canonical — one row per real company, no Inc/LLC noise.
- No duplicate people across sources; the surviving row is the most complete and most senior.
- Every senior title has a business email, not a free-mail domain.
- Every row carries a trigger and a date, so freshness can actually be scored.
- You can re-run the whole pipeline from raw without losing your manual edits.
That is the whole foundation. It is not the play that wins the meeting — it is the one that makes every other play honest. Build it once, keep it running, and the attribution, ABM, and intent playbooks that follow finally have something true to stand on.