---
name: moltsets-rb2b-non-icp-to-icp
description: Use this skill when the user has an RB2B visitor export and wants to find contacts at companies where the identified visitor isn't ICP themselves.
---

# RB2B Non-ICP Visitors to Company Contacts

Filter RB2B visitor rows by "company fit but visitor isn't ICP", then return and enrich contacts at those companies.

## Triggers

"the people who visited aren't my buyers but their companies are a fit - find contacts there", "some visitors aren't ICP but I want to prospect into their companies", "filter out non-ICP visitors but keep their companies", "find contacts at companies that visited even if the visitor isn't the right person".

## CSV parsing

Read the uploaded or pasted CSV. Filter to `ProfileType = Person` rows only. Extract:
- `LinkedInUrl`, `FirstName`, `LastName`, `Title` - visitor identity
- `CompanyName`, `Website` - company identity
- `AllTimePageViews`, `RecentPageCount` - intent signals

## 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 - Define non-ICP visitors

Need one definition before proceeding:

**Non-ICP definition** (which visitor rows to filter out of the RB2B export):
> "Which visitor titles/roles are NOT your ICP? For example: 'engineers', 'support staff', 'interns'. I'll filter visitors matching these out and keep their companies."

If user can't specify non-ICP, flip it: ask for the ICP definition and treat everything else as non-ICP.

**Note:** `search_people` accepts both company and people filters: `company` (name), `company_domain` (exact), `country`, `state`, `city`, `seniority`, `department`/`functional_area`, `industry`, `linkedin_industry`, `naics_code`, `employee_range`, `revenue_range`, `query` (free-text name/role), plus `limit`/`offset`. When you search the fitting companies for ICP contacts, use the ICP definition as `seniority`/`department` filters (enum values) to pull only the right roles - filters cost nothing on top of the call, so scope tightly rather than filtering client-side afterwards.

## `search_people` filter notes

- **Filters are free precision.** Execution cost comes from the free-text `query`, not the filters attached to it. A filters-only call (no `query`) runs **~40x cheaper**; a bare `query` is the least precise shape available and no cheaper than a filtered one. Never send `query` alone - at minimum attach `country` (~99% filled). Billing is unchanged either way: tokens are charged per record returned.
- **Full filter set:** `query`, `company`, `company_domain`, `country`, `state`, `city`, `seniority`, `department` *or* `functional_area` (same underlying data - never both), `industry`, `linkedin_industry`, `naics_code`, `employee_range`, `revenue_range`, plus `limit`/`offset`.
- **`employee_range` and `revenue_range` apply to the person's current employer**, so you can target by company size or revenue without a `search_companies` pass first. Prefer the numeric bands over legacy `"Small"`/`"Mid-Market"`/`"Enterprise"`/`"Unknown"`.
- **Three industry vocabularies:** `industry` (21 broad buckets), `linkedin_industry` (LinkedIn's ~150 niche labels), `naics_code` (any hierarchy level - "54" broad, "541120" surgical). Pick the one matching how specific the targeting actually is.
- **Fill rates:** `country` ~99%, `title` ~85%, `headline` ~65%, `seniority`/`industry`/`department` ~60%, `naics_code`/`linkedin_industry` ~50%. Every filter is exact-match and silently drops records with an empty field, so stacking three sparse ones can zero out a viable audience. Widen by dropping the sparsest filter first, not `country`.
- **Exact strings:** `"C Suite"` (space, not hyphen), `"Marketing & Advertising"`, `"Medical & Health"`, `"Texas"` not `"TX"`, `"United Kingdom"` not `"UK"`.
- **Size before you walk.** `search_linkedin_profile` with `count_only: true` returns a match count free of tokens; every search response carries `results.total`. Results are `_score`-ranked - the first page is the best page, 25 max per call.
- **Emails are already in the results** - records carry `business_email` and `business_email_risk_score`, so those rows need no enrichment call.

## Step 1 - Filter non-ICP visitors

From the Person rows, identify visitors whose `Title` does NOT match the ICP definition. Use keyword matching on `Title` - be generous, flag uncertain cases.

Report:
```
Total Person visitors: X
Non-ICP visitors (title mismatch): X
ICP visitors (kept for direct enrichment if needed): X
```

Ask: "Want me to also enrich the ICP visitors directly while I'm at it?"

## Step 2 - Extract companies

From the non-ICP visitor rows, extract unique companies (`CompanyName` + `Website`).

For companies that appear multiple times (multiple non-ICP visitors from same org), note the total visit count - higher = more intent.

Sort descending by total `AllTimePageViews` across visitors from each company.

Report: "Found X unique companies from non-ICP visitors."

## Step 3 - Credit check

Call `MoltSets:get_billing` and `MoltSets:get_usage` with `period: billing_cycle` in parallel (free).

Estimate:
```
X companies × ~3 results per company = ~X prospects
X prospects × enrich cost per tool   = ~XXX credits
Current balance:                     = XXX credits
```

## Step 4 - Confirm scope

Confirm with user:
> "I'll pull contacts at X companies (sorted by visit intent). How many contacts per company? (Default: 3, max 25)"

Wait for confirmation.

## Step 5 - Probe (first company)

Run `search_people` with `limit: 1` for the highest-intent company, scoped by `company_domain` (the company's `Website`, most precise) or `company`/`query` (company name), optionally narrowed to the ICP role with `seniority`/`department`/`functional_area`/`country` (use enum values). Title also returns as a response field for ranking. Check result quality before scaling.

## Step 6 - Loop all companies

For each company (ordered by intent signals desc):
- `search_people` scoped by `company_domain` (preferred) or `company`/`query` (company name), optionally narrowed to the ICP role with `seniority`/`department`/`functional_area`/`country`
- Tag results with source visitor name + RB2B visit count for traceability
- Flag zero-result companies, do not stop loop

## Step 7 - Deduplicate

Remove duplicate LinkedIn URLs. Report: "Found X unique contacts across Y companies."

## Step 8 - Enrich

Business email is often already on the search records - check first and only enrich the gaps. Credit confirm: "Found X contacts. Enriching [email/phone/both] = ~XXX credits. Proceed?"

Run in parallel:
- `MoltSets:linkedin_to_business_email` - SINGLE `linkedin_url`; one call per URL in parallel (the email gaps)
- `MoltSets:linkedin_to_mobile_phone` with `linkedin_urls: [array]` (the only batch-array endpoint; if requested)

Fire the per-URL email calls in parallel; chunk the phone array at 100 if needed.

## Step 9 - Output

| Visitor (RB2B) | Visitor Title | Company | RB2B Visits | Contact | Contact Title | Business Email | Risk | Mobile |
|---|---|---|---|---|---|---|---|---|

- `Visitor (RB2B)` = the person who actually visited (non-ICP signal source)
- `Contact` = the person found via MoltSets at that company
- `Risk` = the A-F deliverability grade on `Business Email`

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.

Summary:
```
Visitors processed:   X
Non-ICP visitors:     X
Unique companies:     X
Contacts found:       X  (avg X per company)
Zero-result accounts: X
Business emails:      X/N (XX%)
Mobile numbers:       X/N (XX%)  - if enriched
Credits used:         ~XXX

Email risk scores:
  A: X   B: X   C: X   D: X   F: X
  kept (accepted grades):  X
  filtered (excluded):     X
```

## Edge cases

- Visitor title is empty to treat as unknown, include in non-ICP set (safer to include than miss)
- Same company appears via ICP and non-ICP visitors to note both; ICP visitor enriched separately if user opts in
- Credits run out mid-loop to stop, output completed rows, report exact resume point
