inbound-lead-qualifier-skill
About inbound-lead-qualifier-skill
A Claude skill that qualifies, scores, and routes inbound leads using HubSpot data and enrichment. It stages first-touch email as a Gmail draft and requires checks before customer-facing claims.
Key features
- 01HubSpot contact and company lookup
- 02Qualification gates and lead scoring
- 03Enrichment for qualified leads
- 04Tier-based routing with response SLAs
- 05Gmail draft and HubSpot follow-up proposal
Use cases
- 01Triage demo and pricing inquiries
- 02Prioritize leads by fit, intent, and urgency
- 03Route existing customers or partner inquiries
- 04Prepare a reviewed first-touch email
MCP support
Uses MCP tools for HubSpot lookup, qualification, enrichment, product search, copy review, and Gmail draft creation; customer-facing email is staged, never sent.
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ scientiacapital/ skills --skill inbound-lead-qualifier-skillAfter installing, type/inbound-lead-qualifier-skillin Claude Code to confirm it loaded.
Bundled filesconfig.json
Details
From the project READMEInbound Lead Qualifier & Router
Turn an inbound signal into a decision in under two minutes: pull the contact from HubSpot, let `qualify_lead` decide whether it is a lead at all (customer, channel, product-only engager, AE-owned, suppressed) and what tier it carries, enrich what is missing, score ICP fit + intent + urgency to a 0-100 priority with a response SLA, route it (Tim works it, or demo booked and handed to an AE), and stage the first-touch email as a Gmail draft. Speed to lead: the whole run should finish inside the 60-minute speed-to-lead window CLAUDE.md sets. Keywords: `inbound lead`, `qualify inbound`, `score lead`, `demo request`, `form fill`, `triage`<quick_start> Trigger: "qualify this inbound lead", "score and route [name] from [company]", "triage inbound". Input: name + company, or email, or a pasted form notification. Optional: source, form fields.
Tool order:
hubspot_search_contacts→hubspot_get_contact(Epiphan AI) — the record, its conversions, page views, owner, lifecycle.hubspot_get_companyfor the account.qualify_lead(Epiphan AI) — Gate Q. Verdict decides exclude / STALE AE LEAD / tier before anything is scored.enrich_contact(Epiphan AI) — title, seniority, org size, LinkedIn for rows that passed Gate Q. Fallback:apollo_people_match(Apollo plugin).- Score (Stage 4) → route (Stage 5).
- Gate S (
search_product_catalog+ Epiphan Knowledgesearch) on any product the form names; Gate B (get_writing_styleonce,check_my_copyper string) on the email and call opener. - Deliver: Gmail
create_draftfrom[email protected](never send); HubSpot note + task, confirm-first; AE path →ae-handoff-brief-skill. </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>
- Contact resolved in HubSpot (or flagged
not in HubSpotand scored from the pasted form) -
qualify_leadverdict recorded: excluded-with-reason,STALE AE LEAD, or tier ATL/GRAY/BTL - Enrichment attempted only for leads that passed Gate Q
- Score 0-100 with the three components shown and the inputs that produced them
- Priority tier + SLA + route (Tim / AE via ae-handoff-brief / CSM / channel) with one-line reason
- Qualification questions (must-ask + good-to-ask) tied to the vertical and the form's stated need
- Product lines in the email/opener passed Gate S; every customer-facing string passed
check_my_copy - First-touch email exists as a Gmail draft; HubSpot note/task proposed, not written silently
- Outcome sidecar + run-log line written </success_criteria>
Stage 1: Resolve the Lead in HubSpot
MCP Tools: mcp__claude_ai_Epiphan_Ai__hubspot_search_contacts (by email, else name + company) →
mcp__claude_ai_Epiphan_Ai__hubspot_get_contact → mcp__claude_ai_Epiphan_Ai__hubspot_get_company.
Pull: firstname, lastname, email, phone, jobtitle, company, country, lifecyclestage, hs_lead_status, hubspot_owner_id, first_conversion_event_name, recent_conversion_event_name, recent_conversion_date, hs_analytics_num_page_views, hs_analytics_last_url, notes_last_updated, device_count, is_channel, company industry, numberofemployees, domain.
- Multiple matches → take the record with the most recent conversion; list the others as possible duplicates for Gate Q.
- No match (lead arrived as a pasted form notification) → continue with the form data, mark
not in HubSpot, and proposehubspot_create_contactat delivery (confirm-first). - Source precedence: the form/conversion name in HubSpot beats what the user typed.
Stage 2: Gate Q — qualify_lead decides whether this is a lead
MCP Tool: mcp__claude_ai_Epiphan_Ai__qualify_lead on the contact (and the company when the
contact is new).
| Verdict | Route (stop scoring) |
|---|---|
Existing customer (device_count >= 1 / lifecycle customer) | Expansion or support, not inbound: hand to the owning AE/CSM with a one-line note. No draft to the prospect. |
| Channel partner | Channel manager; no draft. |
| Product-only engager (setup / registration form, not demo or pricing) | Support ticket, not a lead. Note it; stop. |
| AE-owned, activity < 90 days | Notify the owning AE (Slack/email note), no BDR draft. |
| AE-owned, stale > 90 days | STALE AE LEAD — score it, present to Tim with the AE name; Tim decides whether to re-engage. |
Suppressed (bdr_suppressed / local suppression file) | Stop; say why. New inbound engagement is itself a release signal — flag for Tim rather than silently reviving. |
| Pass | Continue. Tier (ATL / GRAY / BTL / NEVER) comes from this verdict; taxonomy in CLAUDE.md, not restated here. |
Non-NA leads: flag, still score, route to the regional owner per identifiers.md.
qualify_lead unavailable after one retry → produce the score with [Gate Q unverified], no draft,
no routing, outcome partial, alert Tim.
Stage 3: Enrich What Is Missing
MCP Tool: mcp__claude_ai_Epiphan_Ai__enrich_contact for title, seniority, department, LinkedIn,
org size. Fallback mcp__plugin_sales_apollo__apollo_people_match. Skip if HubSpot already has
title + company size. Enrichment failure is a partial, never a stop — score with what is known
and mark the missing inputs.
Stage 4: Score
ICP Fit (0-40) — Epiphan verticals, not generic SaaS:
- Vertical ICP (0-10): map the CLAUDE.md vertical score (Higher Ed 90, Courts/Legal 85, Government 80, Corporate AV 80, Healthcare 75, Houses of Worship 70, K-12 65) ÷ 9; unknown vertical = 4.
- Buyer tier (0-10): ATL 10 · GRAY 6 · BTL 3 · NEVER 0 (from Gate Q).
- Org fit (0-10): multi-room / multi-site / recurring capture need visible in form, company size, or site = 8-10; single-room hobby use = 2-4.
- Budget signal (0-10): stated budget or quote request 10; active grant / fiscal-year language 7; prior deal history 6; none 3.
Intent (0-30): demo request 30 · pricing/quote 20 · content download or webinar 10 · general inquiry 5. Add +5 if hs_analytics_num_page_views > 5 in the last 30 days (cap 30).
Urgency (0-30): "ASAP" / dated event 30 · timeline stated 20 · "exploring" 10 · none 5.
| Total | Priority | SLA | Default route |
|---|---|---|---|
| 80-100 | 🔴 HOT | call within 1 hour | ATL/GRAY → book demo, hand to AE via ae-handoff-brief-skill; BTL → Tim calls, finds the EB |
| 60-79 | 🟡 WARM | call within 24 hours | Tim works it; demo once EB and timeline confirmed |
| 40-59 | 🟢 COOL | email first, then call | Tim; Gmail draft + Nooks sequence proposal |
| < 40 | ⚪ NURTURE | no call | marketing nurture; note on contact |
Stage 5: Route
- AE path (HOT + ATL/GRAY, or any deal the form sizes above the BDR threshold): pick the AE by
territory/book in
reference/identifiers.mdof the house-rules skill; do not write owner IDs here. Hand off withae-handoff-brief-skill; owner change is confirm-first (hubspot_update_contact_owner). - Tim path (everything else that passed Gate Q): Tim is the BDR of record; set the call task.
- Existing customer / channel / support: per the Stage 2 table. Give one line of "why this route" (tier, vertical, score, territory).
Stage 6: Gates S and B before any customer-facing text
- S: for every product the form or the opener names:
search_product_catalog(Epiphan AI) → Epiphan Knowledgesearch/fetch. Discontinued products get an EOL acknowledgement and a current-lineup pointer, never a spec. No live result →[spec unverified]andpartial. - Competitor lines (Extron, Matrox, Blackmagic, Crestron, vMix, Teradek): verified the same way or left out — "probably comparing to X" is allowed only as an internal note to Tim, never in the draft.
- B:
get_writing_styleonce per session;check_my_copyon the email subject, body, and the call opener. Apply corrections before staging. Brand MCP down → stage with[brand check skipped].
Stage 7: Deliver
- Gmail draft —
mcp__claude_ai_Gmail__create_draft, from[email protected], to the lead. SubjectRe: <form/demo request> — <Company>. Body: one line acknowledging the specific form and what they asked for, one verified product line only if relevant, one calibrated question, a 15-minute ask. Neversend_message. - HubSpot — propose, then on confirmation:
hubspot_create_contact_note("Inbound scored /100 ; route ; reason …") andhubspot_create_contact_taskfor the call inside the SLA.not in HubSpotleads →hubspot_create_contactfirst (confirm-first). - Chat output (the decision card):
# Inbound: <Name> — <Company> Source: <form> · Received: <ts>
Score <n>/100 🔴/🟡/🟢/⚪ SLA: <x> Gate Q: pass | STALE AE LEAD (<AE>) | excluded: <reason>
ICP <n>/40 (vertical <v> · tier <t> · org <o> · budget <b>) · Intent <n>/30 · Urgency <n>/30
Route: <Tim | AE <name> via ae-handoff-brief | CSM | channel> — why: <one line>
Must-ask: 1. what is driving this now 2. current setup / what breaks 3. timeline + event
4. who signs Good-to-ask: success picture · budget range · alternatives seen
Call opener (brand-checked): "<2 sentences>"
Draft: <Gmail draft id> HubSpot: note + task proposed (confirm) Flags: [spec unverified] [brand check skipped] [not in HubSpot]
qualify this inbound lead: Maria Lopez, Director of Academic Technology, Riverside Community College, demo request form, "need lecture capture in 12 rooms by fall"
HubSpot hit, qualify_lead pass, tier ATL. Score 93 (ICP 38: Higher Ed 10, ATL 10, 12 rooms 10, fiscal-year 8 · Intent 30 · Urgency 25). 🔴 HOT, 1-hour SLA, route AE via ae-handoff-brief (NA, Higher Ed). Pearl Nexus line verified via search_product_catalog + Epiphan Knowledge. Gmail draft staged, note + task proposed.
score and route Dan Wu from Acme Studios, pricing page form
qualify_lead: existing customer (device_count 2). Not scored as inbound; routed to owning AE as expansion with a one-line note. No prospect draft.
<anti_patterns>
- Scoring from the pasted text alone when the contact exists in HubSpot.
- Writing a call script with product or competitor claims that never passed Gate S.
- Hardcoding AE names/owner IDs in this file — identifiers.md owns them.
- Sending the first-touch email instead of drafting it.
- Re-implementing the suppression check inline;
qualify_leadalready applies it. </anti_patterns>
<self_healing>
success = resolved, gated, scored, routed, draft staged. partial = decision card produced but a
stage degraded (enrichment failed, Gate S/B skipped and flagged, HubSpot note not confirmed, Gate Q
unverified → no draft). error = lead could not be identified at all (no name/company/email
usable). Ladder per ~/.claude/skills/epiphan-house-rules-skill/reference/self-healing.md: retry
once → degrade with a flag → alert Tim → halt. Append one line per run to
~/.claude/skill-runs/inbound-lead-qualifier-skill.jsonl.
</self_healing>
Emit Outcome Sidecar
As the final step, write to ~/.claude/skill-analytics/last-outcome-inbound-lead-qualifier-skill.json:
{"ts":"[UTC ISO8601]","skill":"inbound-lead-qualifier-skill","version":"<frontmatter version>","variant":"default",
"status":"[success|partial|error]","runtime_ms":[estimated ms from start],
"metrics":{"leads_qualified":[n],"hot_leads":[n],"warm_leads":[n],"cool_leads":[n],"nurture":[n],"excluded_by_qualify":[n],"stale_ae_surfaced":[n],"routed_to_ae":[n],"drafts_created":[n],"avg_score":[n]},
"error":null,"session_id":"[YYYY-MM-DD]"}
Use status "partial" if some stages failed but results were produced; error must name the stage. Use "error" only if no output was generated.