<quick_start>
Trigger: scheduled Mon-Fri 6:00 AM ET (schedule in config.json) or "Run prospect enrich".
Dependencies: Epiphan AI MCP (SQL, qualify_lead, HubSpot writes), Apollo credits, Clay credits, Slack DM to Tim.
Tool order:
execute_sql(Epiphan AI) — candidate pull:phone IS NULL, open lead statuses, USA/Canada. Readcrm://schema.mdfirst; do not guess column names.qualify_lead(Epiphan AI) per candidate — Gate Q. Verdict decides exclude /STALE AE LEAD/ ATL-BTL tier. No credit is spent before this call.- Dedupe gate — skip contacts with
phone_verified_ator an enrichment note newer than 30 days. apollo_people_bulk_match(Apollo plugin, batches of 10) →find-and-enrich-contacts-at-company(Clay) for Apollo misses.hubspot_update_contact+hubspot_create_contact_note(Epiphan AI) — write phone, source, tier tag.- Deliver: HTML report to
~/.claude/skill-state/prospect-enrich/<date>.html, Slack DM to Tim with the path and the summary line, JSON handoff for the waterfall + morning-brief. </quick_start>
<house_rules> Customer-facing skill. Before output, run the gates in order (details: ~/.claude/skills/epiphan-house-rules-skill/reference/gates.md): S: search_product_catalog (Epiphan AI) or Epiphan Knowledge search before any product claim. B: check_my_copy on every customer-facing string. Q: qualify_lead right after the candidate pull, before tiering/enrichment/drafts. D: outbound text becomes a Gmail draft, never a send. Identifiers (owner IDs, portal, emails, seats) come from reference/identifiers.md in that skill — do not restate them here. On any gate failure follow reference/self-healing.md (retry, degrade with a flag, alert Tim, halt) and append a line to ~/.claude/skill-runs/.jsonl. </house_rules>
<success_criteria>
- Candidate pull via
execute_sqlreturns contacts with phone = null (geo: USA/Canada only) - Every candidate passed through
qualify_leadbefore any Apollo/Clay call; excluded counts reported by reason - DEMO REQUEST contacts (first_conversion contains demo/pricing keywords) enriched FIRST
- Tier (ATL/GRAY/BTL/NEVER) taken from the
qualify_leadverdict, not from an inline keyword list - Contacts enriched within the last 30 days skipped (no repeat credit spend)
- Apollo bulk match, then Clay waterfall only for Apollo misses
- Enriched phones synced to HubSpot with tier tag and source
- Company enrichment (domain, revenue, headcount) for ATL-tier contacts
- Branded HTML report written, Slack DM sent to Tim with the link, handoff JSON written
- Report: total processed, phone match rate %, tier breakdown, credits used, excluded-by-gate counts
- Outcome sidecar + run-log line written </success_criteria>
Stage 1: Pull Phoneless Candidates
MCP Tool: mcp__claude_ai_Epiphan_Ai__execute_sql (preferred: one query, up to 1,000 rows). Read crm://schema.md or call get_schema for hubspot_contacts / hubspot_companies before writing SQL.
-- strategy: phoneless open leads in USA/Canada, newest first; AE-owner / customer / channel
-- exclusions are NOT applied here — qualify_lead (Gate Q) owns those verdicts.
SELECT c.id, c.firstname, c.lastname, c.email, c.jobtitle, c.company, c.country,
c.first_conversion, c.lifecyclestage, c.hs_lead_status, c.hubspot_owner_id,
c.phone_verified_at, c.createdate
FROM hubspot_contacts c
WHERE (c.phone IS NULL OR c.phone = '')
AND c.hs_lead_status IN ('Subscriber','Qualified Lead','Inbound Lead','Marketing Qualified Lead','Sales Qualified Lead')
AND c.country IN ('United States','USA','US','Canada','CA')
ORDER BY c.createdate DESC
LIMIT 300;
Fallback if SQL is unavailable after one retry: hubspot_search_contacts cannot filter on phone = null, so degrade to the handoff list from the previous run (~/.claude/skill-state/prospect-enrich/last-candidates.json) and mark the run partial (stage: pull).
Output: candidate list; no credits spent yet.
Stage 2: Gate Q — qualify_lead (replaces inline Golden Rules, suppression, and ATL/BTL tables)
MCP Tool: mcp__claude_ai_Epiphan_Ai__qualify_lead with contactId (or email) per candidate. writeToHubSpot stays false.
The verdict is authoritative for: existing customer / device owner, channel partner, product-only engager (setup forms), AE-owned active vs STALE AE LEAD, suppression, geo, junk, and the ATL / GRAY / BTL / NEVER power level. Rules, owner IDs, and the keyword tables live in ~/.claude/skills/epiphan-house-rules-skill/reference/gates.md and reference/identifiers.md; do not restate them here.
Keep per contact: verdict (keep / exclude / stale_ae), exclude_reason, tier. STALE AE LEAD contacts are kept, tagged, and listed in their own report section for Tim's decision; they are not enriched automatically.
If qualify_lead is unavailable after one retry: halt (this skill produces a lead list), write the state file, Slack DM Tim, status error (stage: gate_q).
Stage 3: Priority Order (DEMO REQUEST first)
DEMO REQUEST is an intent signal layered on top of the Gate Q tier, not a replacement for it:
first_conversioncontains any of'Demo signup','Pearl family demo','demo request','General contact us','pricing'→ DEMO REQUEST (enrich first regardless of tier, unless Gate Q excluded the contact).- Otherwise order by Gate Q tier: ATL → GRAY → BTL. NEVER tier is skipped (no credit spend) and listed in the report.
Dedupe gate (before any credit spend): skip the contact if phone_verified_at is within 30 days, or the most recent contact note starts with [prospect-enrich] and is within 30 days. Count these as already_enriched in the report.
Stage 4: Batch Enrich via Apollo
MCP Tool: mcp__plugin_sales_apollo__apollo_people_bulk_match (up to 10 per call; reveal_personal_emails: false).
Pass id, first_name, last_name, email, organization_name, and the domain from the email or company website. Keep phone (direct, mobile, corporate), person_seniorities, and tenure. Surface the mcp_credits block from the response in the report. Target match rate: 65-75%.
Retry once on rate limit / timeout. If Apollo stays down: skip to Clay for every contact, mark partial (stage: apollo).
Stage 5: Waterfall Misses to Clay
MCP Tool: Clay find-and-enrich-contacts-at-company with companyIdentifier = domain, contactFilters.job_title_keywords from the contact's title, locations: ["United States","Canada"], and contactDataPoints: [{type:"Email"}] plus a custom phone data point.
If Clay credits are exhausted or the call fails after one retry: continue with Apollo-only results, mark partial (stage: clay), and put "Clay failing — check credits" in the Slack DM.
Stage 6: Sync Back to HubSpot
For each contact with a phone: mcp__claude_ai_Epiphan_Ai__hubspot_update_contact → phone, phone_source (apollo | clay), phone_verified_at = now. Then hubspot_create_contact_note with [prospect-enrich] <date> tier=<ATL|GRAY|BTL> source=<apollo|clay> demo_request=<y|n>. That note is what Stage 3's dedupe gate reads next run.
For ATL-tier contacts also run apollo_organizations_enrich on the domain and update the company (revenue, headcount, founded) via hubspot_update_company when those fields are empty.
If HubSpot writes fail after one retry: keep the enriched rows in the handoff JSON with synced:false, mark partial (stage: sync), and say so in the DM.
Stage 7: Report, Deliver, Hand Off
Report: self-contained HTML at ~/.claude/skill-state/prospect-enrich/prospect-enrich-<YYYY-MM-DD>.html. Styling via get_brand_asset_kit({asset_type:'report'}) (Epiphan Brand) when available; otherwise plain Epiphan palette and mark partial (stage: brand). Internal report, so no Gate S/B per row; any product sentence in the Insights section passes search_product_catalog first.
Sections: summary metrics table (pool, excluded by Gate Q with reason counts, already_enriched skipped, DEMO / ATL / GRAY / BTL / NEVER counts, Apollo hits %, Clay hits %, total enriched %, credits used), then DEMO REQUEST (green), STALE AE LEAD (amber, with recommended action for Tim), ATL (blue), GRAY (yellow), BTL (gray), NEVER skipped list, Key Insights.
Delivery (mandatory, otherwise nobody sees a 6 AM run):
- Slack DM to Tim (own DM only) with the one-line summary and the report path.
- Handoff JSON
~/.claude/skill-state/prospect-enrich/last-run.json:{ts, enriched:[{contact_id, phone, source, tier, demo_request}], stale_ae:[...], skipped_never:[n], credits}.phone-verification-waterfall-skillandmorning-brief-skillread this file.
Stage 8: Emit Outcome Sidecar + Run Log
Write ~/.claude/skill-analytics/last-outcome-prospect-enrich-skill.json:
{"ts":"[UTC ISO8601]","skill":"prospect-enrich-skill","version":"<frontmatter version>","variant":"default",
"status":"[success|partial|error]","runtime_ms":[estimated ms from start],
"metrics":{"contacts_pulled":[Stage 1],"excluded_by_qualify":[n],"stale_ae_surfaced":[n],"already_enriched_skipped":[n],
"phones_found":[Apollo+Clay],"phone_match_rate_pct":[n],"demo_request_count":[n],"atl_count":[n],"credits_used":[n]},
"error":null,"session_id":"[YYYY-MM-DD]"}
partial / error must name the failing stage in error. No contact names or emails in any field. Also append the run-log line described in reference/self-healing.md to ~/.claude/skill-runs/prospect-enrich-skill.jsonl.
<self_healing>
partial = phones were produced but a stage degraded: SQL pull fell back to the last candidate file, Apollo or Clay failed after retry (continue with the other), HubSpot sync failed (rows kept in handoff with synced:false), brand kit unavailable. error = no candidates could be pulled, or qualify_lead was unavailable (halt: never enrich a list Gate Q has not seen). Ladder per ~/.claude/skills/epiphan-house-rules-skill/reference/self-healing.md: retry once → degrade with a flag → Slack DM Tim → halt with state written to ~/.claude/skill-state/prospect-enrich/. One JSON line per run to ~/.claude/skill-runs/prospect-enrich-skill.jsonl.
</self_healing>
<reference_guides>
~/.claude/skills/epiphan-house-rules-skill/reference/gates.md— Gate Q verdict semantics, suppression, geo default~/.claude/skills/epiphan-house-rules-skill/reference/identifiers.md— owner IDs, portal, emails (never restate here)- Downstream:
phone-verification-waterfall-skill(phoneless re-check),prospect-refresh-skill(net-new search),morning-brief-skill(reads the handoff) </reference_guides>
Changelog
- v1.1.1 (2026-10-04): Gate Q via
qualify_leadreplaces inline Golden Rules / suppression / ATL-BTL tables; 30-day dedupe before credit spend; Slack DM + handoff delivery; self-healing ladder + run log; schedule moved to config.json; frontmatter name = directory. - v1.1 (2026-03-19): DEMO REQUEST tier, HTML report, schedule expanded Mon-Fri.
- v1.0 (2026-03-19): Initial release with Apollo + Clay waterfall.