---
name: moltsets-search-prospects
description: Use this skill when the user wants to find a list of people by their company - company name, exact domain, industry, employee range, or revenue range.
---

# Search Prospects

Find people matching an ICP - by company attributes and by people-level filters (seniority, department, country, industry) - using `MoltSets:search_people`.

## Triggers

"find people at fintech companies", "show me contacts at SaaS companies", "who works at [company]", "search for prospects", "build me a list from [industry]", "find contacts at companies sized [employee range]", "find directors in sales at SaaS companies". Returns ranked contact records, each including title, seniority, country, LinkedIn, and business email. Records often already include `business_email` - enrich only for missing emails or phone via enrich-contact or batch-enrich-linkedin-list.

## Set Expectations

Before firing any calls, give the user a brief, dry heads-up. Deadpan over enthusiastic - no filler, no corporate speak.

Facts to work with:
- 1–2 search calls, results in ~5–10 seconds
- Records usually include `business_email` already - separate enrichment is often unnecessary for business email. If the user needs missing emails or phone, that's a separate step handled by enrich-contact or batch-enrich-linkedin-list

## Risk scores - confirm before enriching

Every email MoltSets returns carries a deliverability risk score - a grade from A to F. Ask which grades to keep before running anything:

> "MoltSets grades email deliverability risk from A (best) to F (no data). Which grades should I keep? I'd recommend **A, B, C and F** - and dropping **D**, since D is a confirmed bounce, complaint or spam trap."

| Grade | Meaning | Best practice |
| --- | --- | --- |
| A | Strongest engagement signal available | Safe to send immediately |
| B | Solid | Fine in regular sends; on a new or warming domain, send a smaller batch before scaling |
| C | Deliverability can't be confirmed | Segment separately from A/B, warm at low volume, watch engagement, suppress non-responders after 1-2 attempts |
| D | Confirmed bounce, complaint or spam trap | Never send - fastest way to trip spam traps and damage sender reputation for the whole list |
| F | No data - unknown risk, not "safe" | Re-verify before sending, or treat like C |

Use whatever set the user confirms. Treat the grade as a filter, not a guarantee - it lowers risk, it doesn't remove it. Pair it with the user's own sending domain reputation and list hygiene.

## Step 0 - Input Quality Gate

`search_people` filters on **both company attributes and people-level attributes**. Assess input before searching. If nothing scopes the search at all (no company anchor and no people filter), **pause and ask**.

### Request parameters
| Field | Param | Example |
|---|---|---|
| Free-text (person name or role keywords) | `query` | "Jane Smith", "VP Sales" |
| Company name | `company` | "Stripe", "HubSpot" |
| Exact domain | `company_domain` | "stripe.com" |
| Country | `country` | "United States", "United Kingdom" |
| State / region | `state` | "Texas", "California" |
| City | `city` | "Portland" (pair with `state` - city names repeat) |
| Seniority | `seniority` | "Director", "VP", "C Suite" |
| Industry (broad) | `industry` | "Information Technology", "Finance and Banking" |
| Industry (niche) | `linkedin_industry` | "Staffing and Recruiting", "Wellness and Fitness Services" |
| Industry (code) | `naics_code` | "54" (sector), "541120" (surgical) |
| Department | `department` | "Sales", "Marketing" |
| Functional area | `functional_area` | "Sales", "Engineering" (use ONE of department/functional_area) |
| Employer size | `employee_range` | "201-500", "1001-5000" |
| Employer revenue | `revenue_range` | "$10M - $20M", "Above $50M" |
| Page size | `limit` | default 10, max 25 (5 on Free) |
| Pagination | `offset` | 0, 25, 50, … |

`query` is free-text across full_name / first / last / company name / title - put a person's **name** or **role keywords** here. Use `department` OR `functional_area`, not both. `employee_range` and `revenue_range` filter on the person's **current employer**, so you can target by company size or revenue in a single `search_people` call - no `search_companies` pass first.

### Enum values (use EXACT strings, casing, punctuation)
- `seniority`: Intern, Entry, Senior, Manager, Director, VP, Head, C Suite, Owner, Partner
- `industry`: Information Technology, Professional and Business Services, Finance and Banking, Education, Health and Pharmaceuticals, Manufacturing, Government and Public Administration, Retail, Food and Beverage, Creative Arts and Entertainment, Non-Profit and Social Services, Transportation and Logistics, Construction, Tourism and Hospitality, Energy, Marketing & Advertising, Telecommunications, Real Estate, Automotive, Media and Publishing, Agriculture
- `department` / `functional_area`: Operations, Sales, Information Technology, Education, Engineering, Finance, Medical & Health, Marketing, Human Resources, Design, Consulting, Legal

### Vague input triggers
Trigger this gate only if the user gives nothing to scope on - no company anchor AND no people filter (seniority/department/country/industry/name), e.g.:
- "find me some people"
- "search for prospects" (no anchor at all)

### Response when input is vague

> "I need at least one thing to search on. Which of these can you give me?
>
> - **Company** - name or domain, e.g. "Stripe", stripe.com
> - **Person name or role** - e.g. "Jane Smith", "VP of Sales"
> - **Seniority** - e.g. Director, VP, C Suite
> - **Department** - e.g. Sales, Marketing, Engineering
> - **Country** - e.g. United States (or state/city)
> - **Industry** - e.g. Information Technology, Finance and Banking
> - **Company size or revenue** - e.g. 51-200 employees, $10M - $20M"

Wait for response before running the search.

### What helps most
- **Domain** (`company_domain`) - most precise way to scope to one company
- **Industry + seniority + department** - strong combo for role-based ICP targeting across many companies
- **Industry + seniority + employee_range + country** - firmographic targeting in one call, no company search needed

### Filters are free precision

Execution cost comes from the free-text `query`, not the filters attached to it.

| Query shape | Relative cost to execute |
|---|---|
| `query` alone | baseline |
| `query` + any number of filters | the same - filters add nothing |
| filters only, no `query` | **~40x cheaper** |

- **Never send a bare `query`.** Attaching `country` costs nothing and removes every other market. A query-only call is the least precise shape available and no cheaper than a filtered one.
- **Drop `query` entirely when filters carry the whole intent.** "VPs of sales at US software companies" is `seniority` + `department` + `industry` + `country` - no free text needed, ~40x cheaper to run.
- Billing doesn't change either way - tokens are charged per record returned. This is about speed and search load.
- If all the user gives you is text, add `country`: ~99% fill rate, so it discards almost no legitimate match.

### Fill rates - don't over-stack sparse filters

Every non-`query` filter is exact-match and silently discards records where the field is empty.

| Filter | Fill rate |
|---|---|
| `country` | ~99% |
| `query` → `title` | ~85% |
| `query` → `headline` | ~65% |
| `seniority` / `industry` / `department` / `functional_area` | ~60% |
| `naics_code` / `linkedin_industry` | ~50% |

Three ~60%-filled filters stacked together can cut a viable audience to nothing. When results come back thin, drop the sparsest filter first (`naics_code`, `linkedin_industry`) or shorten the NAICS code - not `country`.

### Size the search before you walk it

`search_linkedin_profile` accepts `count_only: true` - returns the match count with no data fetched and no tokens charged. Use it to size a search before committing. Every search response also carries `results.total`; read it before paginating.

## Mapping user language to parameters

| User says | Where it goes | Example value |
|---|---|---|
| A person's name | `query` | "Jane Smith" |
| A role / title phrase | `query` | "VP of Marketing", "RevOps lead" |
| Company name | `company` (or `query`) | "Stripe", "HubSpot" |
| Exact company domain | `company_domain` | "stripe.com" |
| Director+ / VP / C-level | `seniority` | "Director", "VP", "C Suite" |
| Sales / marketing / engineering people | `department` or `functional_area` | "Sales", "Marketing", "Engineering" |
| In the US / UK / etc. | `country` | "United States", "United Kingdom" |
| In Texas / on the west coast | `state` (and `city` if named) | "Texas", "California" |
| Tech / SaaS / fintech / healthcare | `industry` | "Information Technology", "Finance and Banking", "Health and Pharmaceuticals" |
| A niche vertical the broad buckets can't express | `linkedin_industry` | "Staffing and Recruiting", "Wellness and Fitness Services" |
| A NAICS code, or "narrow this industry down" | `naics_code` | "54", "541120" |
| At startups / mid-market / enterprise | `employee_range` | "21-50", "201-500", "5001+" |
| At companies doing $X revenue | `revenue_range` | "$10M - $20M", "Above $50M" |

Map the user's words to the **exact enum values** above. Use `department` OR `functional_area`, never both. Exact string values matter - use the casing shown: `"C Suite"` has a space not a hyphen, `"Marketing & Advertising"` and `"Medical & Health"` use ampersands, `"Texas"` not `"TX"`, `"United Kingdom"` not `"UK"` (`"UK"` and `"HK"` exist as dirty values - avoid them).

Three industry filters describe the same thing at different resolutions - pick the one matching how specific the user actually is. `industry` for broad categories, `linkedin_industry` for a niche the buckets can't express, `naics_code` when they want standardised codes or precise control over breadth (shorten the code to widen the net).

## Step 1 - Probe first (cheap)

Run with `limit: 1` to check result quality before fetching more. If the top result looks off-target, adjust `query` or filters before scaling.

## Step 2 - Full search

Default: `limit: 10`. Increase up to 25 (the max per page) if user wants more. Read `results.total` off the response before paginating - it's the count of matching records across all pages, and it tells you whether the search is worth walking. Then paginate with `offset` (0, 25, 50, …) for larger pulls. Results are `_score`-ranked, so the first page is the best page.

## Step 3 - Present results

Each record carries response fields: `full_name`, `title`, `seniority`, `country`, `headline`, `linkedin_url`, `business_email`, company data, and `_score`. Present them:

| Name | Title | Company | Location | LinkedIn | Business email | Risk |
|---|---|---|---|---|---|---|

Show the risk score on every row that has an email, including grades outside the accepted set - flag those rather than dropping them silently, so the user can see what was filtered and why.

Close with the risk-score spread: "Risk scores: A X, B X, C X, D X, F X - X kept, X outside your accepted set."

Omit `_score` unless user asks about match quality. `Risk` is the A-F deliverability grade, not the `_score` match-quality field - don't conflate them.

## After results

`business_email` is often already populated, with its own risk score - call both out and offer to enrich only the rows missing an acceptable-grade email, plus mobile numbers.

Ask: "Some rows already have a business email. Want me to fill the gaps or add mobile numbers?"

If yes: collect LinkedIn URLs for the rows that need it to hand off to **enrich-contact** (for small batches) or **batch-enrich-linkedin-list** (for 10+).

## Edge cases

- Vague input (nothing to scope on) to Step 0 gate fires before any search runs
- Seniority, department, country, and industry are request filters - map the user's words to the exact enum values. Title also comes back as a response field for ranking
- Max 25 per call - for larger lists, use `offset` to paginate
- Search returns fewer results than expected to a sparse filter is probably to blame. Drop `naics_code`/`linkedin_industry` first, shorten a NAICS code, or try the neighbouring `industry` value before widening `country`
- `city` matches the exact stored city, **not** its suburbs - for metro-area coverage filter on `state` or `country` instead
