inbound-lead-qualification
$
npx mdskill add gooseworks-ai/goose-skills/inbound-lead-qualificationScore inbound leads against ICP criteria and output qualified CSVs.
- Validates company size, industry, role, and use case fit.
- Integrates with CRM systems and enrichment data sources.
- Checks for duplicates and existing customer relationships.
- Delivers a scored CSV with status, reasoning, and pipeline flags.
SKILL.md
.github/skills/inbound-lead-qualificationView on GitHub ↗
---
name: inbound-lead-qualification
version: 1.0.0
description: >
Qualifies inbound leads against full ICP criteria — company size, industry, use case fit,
role/seniority of the person. Checks CRM and existing customer base for duplicates and
existing relationships. Outputs a scored CSV with qualification status, reasoning, and
pipeline overlap flags. Tool-agnostic — works with any CRM, enrichment tool, or data source.
tags: [lead-generation]
---
# Inbound Lead Qualification
Takes a set of inbound leads and validates each against your full ICP criteria. Not a fast-pass triage (that's `inbound-lead-triage`) — this is the thorough qualification step that determines whether a lead is genuinely worth pursuing, and produces a scored CSV for the team.
## When to Auto-Load
Load this composite when:
- User says "qualify these inbound leads", "check if these leads are ICP", "score my inbound"
- An upstream triage has been completed and leads need deeper qualification
- User has a batch of leads and wants a qualified/disqualified verdict on each
## Architecture
```
[Inbound Leads] → Step 1: Load ICP & Config → Step 2: CRM/Pipeline Check → Step 3: Company Qualification → Step 4: Person Qualification → Step 5: Use Case Fit → Step 6: Score & Verdict → Step 7: Output CSV
```
---
## Step 0: Configuration (Once Per Client)
On first run, establish the ICP definition and CRM access. Save to the current working directory or wherever the user prefers (e.g., `config/lead-qualification.json`).
```json
{
"icp_definition": {
"company_size": {
"min_employees": null,
"max_employees": null,
"sweet_spot": "",
"notes": ""
},
"industry": {
"target_industries": [],
"excluded_industries": [],
"notes": ""
},
"use_case": {
"primary_use_cases": [],
"secondary_use_cases": [],
"anti_use_cases": [],
"notes": ""
},
"company_stage": {
"target_stages": [],
"excluded_stages": [],
"notes": ""
},
"geography": {
"target_regions": [],
"excluded_regions": [],
"notes": ""
}
},
"buyer_personas": [
{
"name": "",
"titles": [],
"seniority_levels": [],
"departments": [],
"is_economic_buyer": false,
"is_champion": false,
"is_user": false
}
],
"hard_disqualifiers": [],
"hard_qualifiers": [],
"crm_access": {
"tool": "HubSpot | Salesforce | CSV export | none",
"access_method": "",
"tables_or_objects": []
},
"existing_customer_source": {
"tool": "HubSpot | Salesforce | CSV | none",
"access_method": ""
},
"qualification_prompt_path": "path/to/lead-qualification/prompt.md or null"
}
```
**If `lead-qualification` capability already has a saved qualification prompt:** Reference it directly — don't rebuild ICP criteria from scratch.
**On subsequent runs:** Load config silently.
---
## Step 1: Load ICP Criteria & Parse Leads
### Process
1. Load the client's ICP config (or qualification prompt from `lead-qualification` capability)
2. Parse the inbound lead list — accept any format:
- Output from `inbound-lead-triage` (already normalized)
- Raw CSV with any column structure
- Pasted list of names/emails/companies
- CRM export
3. Identify what data is available vs. missing per lead:
- **Have:** Fields present in the input
- **Need:** Fields required for qualification but missing
- **Gap report:** "X leads have company name, Y have title, Z have nothing but email"
### Output
- Parsed lead list with available/missing field inventory
- Gap report for the user
### Human Checkpoint
If >50% of leads are missing critical fields (company name or person title), recommend running `inbound-lead-enrichment` first. Ask: "Many leads are missing company/title data. Want me to enrich them first, or qualify with what's available?"
---
## Step 2: CRM & Pipeline Check
### Process
For each lead, check against existing data sources to identify overlaps:
**Check 1 — Existing customer?**
- Search customer database by company domain/name
- If match found: Flag as `existing_customer` with customer details (plan, account owner, contract status)
- This is NOT a disqualification — it's a routing flag (upsell vs. new business)
**Check 2 — Already in pipeline?**
- Search your CRM (HubSpot, Salesforce, CSV) for the company in active deals
- If match found: Flag as `in_pipeline` with deal details (stage, owner, last activity)
- Critical: Sales rep should know before reaching out that a colleague already has this account
**Check 3 — Previous engagement?**
- Search outreach logs for the email/company
- If match found: Flag as `previously_contacted` with history summary (when, what channel, outcome)
**Check 4 — Known from signal composites?**
- Search your CRM or signal tracking system for the company
- If match found: Flag as `signal_flagged` with signal type and date
### Output
Each lead tagged with:
- `pipeline_status`: `new` | `existing_customer` | `in_pipeline` | `previously_contacted`
- `pipeline_detail`: One sentence explaining the overlap (or null)
- `signal_flags`: Any signal composite matches
### Handling Overlaps
- **Existing customer:** Don't disqualify. Mark separately. Might be expansion/upsell.
- **In pipeline:** Don't disqualify. Flag for sales rep coordination. Note the existing deal owner.
- **Previously contacted but no response:** Still qualify. The inbound signal means they're now warmer.
- **Previously contacted and rejected:** Still qualify the inbound. People change their minds. Note the prior context.
---
## Step 3: Company Qualification
### Process
For each lead's company, evaluate against every ICP company dimension:
**Dimension 1 — Company Size**
- Check employee count against ICP range
- Sources: enrichment data, LinkedIn company page, web search
- Score: `match` | `borderline` | `mismatch` | `unknown`
- Note: If the lead is from a subsidiary or division, evaluate the relevant unit, not the parent company
**Dimension 2 — Industry**
- Check against target and excluded industry lists
- Be smart about classification: "AI-powered HR platform" matches both "AI/ML" and "HR Tech"
- Score: `match` | `adjacent` (related but not core target) | `mismatch` | `unknown`
**Dimension 3 — Company Stage**
- Seed, Series A, Series B+, Growth, Public, Bootstrapped
- Sources: SixtyFour or Orthogonal, news, enrichment data
- Score: `match` | `borderline` | `mismatch` | `unknown`
**Dimension 4 — Geography**
- Check HQ location and/or the specific person's location
- For remote-first companies, check where the majority of the team is
- Score: `match` | `borderline` | `mismatch` | `unknown`
**Dimension 5 — Use Case Fit**
- Based on what the company does, could they plausibly use the product?
- This is the most nuanced dimension — requires understanding both the product and the company's operations
- Sources: company website, product description, job postings (hint at internal tools/processes)
- Score: `strong_fit` | `moderate_fit` | `weak_fit` | `no_fit` | `unknown`
### Output
Each lead gets a `company_qualification` block:
```
{
"company_size": { "score": "", "value": "", "reasoning": "" },
"industry": { "score": "", "value": "", "reasoning": "" },
"stage": { "score": "", "value": "", "reasoning": "" },
"geography": { "score": "", "value": "", "reasoning": "" },
"use_case": { "score": "", "value": "", "reasoning": "" },
"company_verdict": "qualified | borderline | disqualified | insufficient_data"
}
```
---
## Step 4: Person Qualification
### Process
For each lead's contact person, evaluate against buyer persona criteria:
**Dimension 1 — Title/Role Match**
- Check title against buyer persona title lists
- Handle variations: "VP of Marketing" = "Vice President, Marketing" = "VP Marketing"
- Be smart about title inflation at small companies (a "Director" at a 10-person startup ≠ "Director" at a 10,000-person enterprise)
- Score: `exact_match` | `close_match` | `adjacent` | `mismatch` | `unknown`
**Dimension 2 — Seniority Level**
- Map to: Individual Contributor, Manager, Director, VP, C-Level, Founder
- Check against ICP seniority requirements
- Score: `match` | `too_junior` | `too_senior` | `unknown`
**Dimension 3 — Department**
- Engineering, Product, Marketing, Sales, Operations, Finance, HR, etc.
- Check against ICP department targets
- Score: `match` | `adjacent` | `mismatch` | `unknown`
**Dimension 4 — Authority Type**
- Based on title + seniority, classify:
- `economic_buyer` — Can sign the check
- `champion` — Wants it, can influence the decision
- `user` — Would use it daily, can validate need
- `evaluator` — Tasked with research, limited decision power
- `gatekeeper` — Can block but not approve
- `unknown`
**Dimension 5 — Right Person, Wrong Company (or Vice Versa)**
- If company qualifies but person doesn't: Flag as `right_company_wrong_person` — this is a referral opportunity
- If person qualifies but company doesn't: Flag as `right_person_wrong_company` — rare for inbound, but possible with job changers
### Output
Each lead gets a `person_qualification` block:
```
{
"title_match": { "score": "", "value": "", "reasoning": "" },
"seniority": { "score": "", "value": "", "reasoning": "" },
"department": { "score": "", "value": "", "reasoning": "" },
"authority_type": "",
"person_verdict": "qualified | borderline | disqualified | insufficient_data",
"mismatch_type": "null | right_company_wrong_person | right_person_wrong_company"
}
```
---
## Step 5: Use Case Fit Assessment
### Process
This step connects the company's likely needs to your product's actual capabilities. It goes deeper than Step 3's company-level use case check.
1. **Infer the lead's intent** from their inbound action:
- Demo request message → What did they say they need?
- Content downloaded → What topic were they researching?
- Webinar attended → What problem were they trying to solve?
- Free trial signup → What feature did they try first?
- Chatbot conversation → What questions did they ask?
2. **Map intent to product capabilities:**
- Does the product actually solve what they seem to need?
- Is this a primary use case or a stretch?
- Are there known limitations that would disappoint them?
3. **Assess implementation feasibility:**
- Based on company size and stage, can they realistically implement?
- Do they likely have the technical resources / team to adopt?
- Any known blockers for companies like this? (e.g., "banks need SOC2 and we don't have it yet")
### Output
```
{
"inferred_intent": "",
"intent_source": "",
"product_fit": "strong | moderate | weak | unknown",
"product_fit_reasoning": "",
"implementation_feasibility": "easy | moderate | complex | unlikely",
"known_blockers": []
}
```
---
## Step 6: Score & Verdict
### Scoring Logic
Combine all dimensions into a final qualification verdict.
**Composite Score Calculation:**
| Dimension | Weight | Possible Values |
|-----------|--------|-----------------|
| Company Size | 15% | match=100, borderline=50, mismatch=0, unknown=30 |
| Industry | 20% | match=100, adjacent=60, mismatch=0, unknown=30 |
| Company Stage | 10% | match=100, borderline=50, mismatch=0, unknown=30 |
| Geography | 10% | match=100, borderline=50, mismatch=0, unknown=30 |
| Use Case Fit | 25% | strong=100, moderate=60, weak=20, no_fit=0, unknown=30 |
| Person Title/Role | 15% | exact=100, close=75, adjacent=40, mismatch=0, unknown=30 |
| Person Seniority | 5% | match=100, too_junior=20, too_senior=60, unknown=30 |
**Hard overrides (bypass the score):**
- Any hard disqualifier present → `disqualified` regardless of score
- Any hard qualifier present → `qualified` regardless of score (but still show the full breakdown)
- Existing customer → Route separately, don't score as new lead
**Verdict thresholds:**
- **Score ≥ 75:** `qualified` — Pursue actively
- **Score 50-74:** `borderline` — Qualified with caveats, may need manual review
- **Score 30-49:** `near_miss` — Not qualified now, but close enough to consider (referral or nurture)
- **Score < 30:** `disqualified` — Does not fit ICP
**Sub-verdicts for routing:**
- `qualified_hot` — Score ≥ 75 AND Tier 1/2 urgency from triage
- `qualified_warm` — Score ≥ 75 AND Tier 3/4 urgency
- `borderline_review` — Score 50-74, needs human judgment call
- `near_miss_referral` — Score 30-49 AND right_company_wrong_person (referral opportunity)
- `near_miss_nurture` — Score 30-49, might fit in the future
- `disqualified_polite` — Score < 30, needs polite decline
- `disqualified_competitor` — Competitor employee
- `existing_customer_upsell` — Existing customer with expansion signal
### Output
Each lead gets:
```
{
"composite_score": 0-100,
"verdict": "",
"sub_verdict": "",
"top_qualification_reasons": [],
"top_disqualification_reasons": [],
"summary": "One sentence: why this lead is/isn't a fit"
}
```
---
## Step 7: Output CSV
### CSV Structure
Produce a CSV with ALL input fields preserved plus qualification columns appended:
**Core qualification columns:**
- `qualification_verdict` — qualified | borderline | near_miss | disqualified
- `qualification_sub_verdict` — qualified_hot | qualified_warm | borderline_review | near_miss_referral | near_miss_nurture | disqualified_polite | disqualified_competitor | existing_customer_upsell
- `composite_score` — 0-100
- `summary` — One sentence qualification reasoning
**Pipeline check columns:**
- `pipeline_status` — new | existing_customer | in_pipeline | previously_contacted
- `pipeline_detail` — One sentence on the overlap
- `signal_flags` — Any signal composite matches
**Company qualification columns:**
- `company_size_score` — match | borderline | mismatch | unknown
- `industry_score` — match | adjacent | mismatch | unknown
- `stage_score` — match | borderline | mismatch | unknown
- `geography_score` — match | borderline | mismatch | unknown
- `use_case_score` — strong | moderate | weak | no_fit | unknown
**Person qualification columns:**
- `title_match_score` — exact_match | close_match | adjacent | mismatch | unknown
- `seniority_score` — match | too_junior | too_senior | unknown
- `authority_type` — economic_buyer | champion | user | evaluator | gatekeeper | unknown
- `mismatch_type` — null | right_company_wrong_person | right_person_wrong_company
**Use case columns:**
- `inferred_intent` — What they seem to need
- `product_fit` — strong | moderate | weak | unknown
- `implementation_feasibility` — easy | moderate | complex | unlikely
### Save Location
The current working directory or wherever the user prefers (e.g., `leads/inbound-qualified-[date].csv`).
### Summary Report
After producing the CSV, present a summary:
```markdown
## Inbound Lead Qualification: [Period]
**Total leads processed:** X
**Qualified:** X (Y%) — X hot, X warm
**Borderline (manual review):** X (Y%)
**Near miss:** X (Y%) — X referral opportunities, X nurture
**Disqualified:** X (Y%)
**Pipeline overlaps:**
- Existing customers: X (route to CS)
- Already in pipeline: X (coordinate with deal owner)
- Previously contacted: X (now warmer — re-engage)
**Top qualification reasons:**
1. [reason] — X leads
2. [reason] — X leads
**Top disqualification reasons:**
1. [reason] — X leads
2. [reason] — X leads
**Data quality:**
- Leads with full data: X
- Leads with partial data (some dimensions scored as 'unknown'): X
- Leads needing enrichment: X
**CSV saved to:** [path]
```
---
## Handling Edge Cases
**Lead with only an email (no name, no company):**
- Extract company domain from email
- If corporate domain: look up the company, proceed with company qualification (person qualification will be mostly "unknown")
- If personal email (gmail, yahoo): Score as `insufficient_data`, recommend enrichment or manual review
**Same company, multiple leads:**
- Qualify the company once, apply to all leads from that company
- Person qualification runs individually for each
- Flag the multi-contact opportunity: "3 people from [Company] came inbound — potential committee buy"
**Contradictory signals:**
- Company is strong fit but person is completely wrong (e.g., intern at a perfect company)
- Score honestly. The sub-verdict `right_company_wrong_person` routes this to referral handling in `disqualification-handling`
**Borderline calls:**
- When the score is 50-74 and could go either way, lean toward qualifying for inbound leads
- Rationale: they came to YOU. The intent signal tips borderline cases toward "worth a conversation"
- Note this lean in the reasoning: "Borderline on [dimension], but inbound intent suggests pursuing"
**Scoring with missing data:**
- Unknown dimensions score at 30 (not 0, not 50) — absence of data is mildly negative but not disqualifying
- If >3 dimensions are "unknown", the lead is `insufficient_data` regardless of score — recommend enrichment first
---
## Tools Required
- **CRM access** — to check pipeline, existing customers, outreach history
- **Web search** — for company research when enrichment data is sparse
- **Enrichment tools** — SixtyFour or Orthogonal, LinkedIn scraper, or similar (optional, enhances accuracy)
- **Read/Write** — for CSV I/O and config management