Lusha
Lusha v3.0.0
Publisher description
From the marketplace listing
Connect Lusha to ChatGPT for B2B sales intelligence, prospecting, and go-to-market data, including verified contacts, decision-makers, and company enrichment, straight into your chat. Lusha is the B2B data layer behind 300M+ verified contacts, used by sales, marketing, RevOps, and recruiting teams for lead generation, CRM enrichment, account research, and outbound campaigns. Backed by GDPR, CCPA, SOC 2 Type II, and ISO 27701 compliance. Use Lusha to: Find contacts and decision-makers by title, seniority, industry, or location: "Find 10 VPs of RevOps at Series B SaaS companies in NYC" Pull verified work emails, direct dials, and mobile numbers with 98% email deliverability and 85% phone accuracy: "Get the verified email and mobile number for the Head of Marketing at Notion" Surface the decision-making buying committee at target accounts: "Find the decision makers I should reach out to at Notion and Figma" Enrich CRM records, leads, and inbound signups with firmographics, technographics, headcount, and revenue: "Enrich these 25 webinar signups with industry, tech stack, and hiring signals" Track buying signals like funding, leadership hires, and tech stack changes: "Show me the buying signals from the last 90 days for these 15 accounts" Get ranked company and contact recommendations scored by lead fit and intent: "Recommend companies that match our best customer profile, ranked by buying signals" Find lookalike accounts based on closed-won customers: "Find 20 companies that look like these 5 customers"
Language: English · Automatically detected from descriptions.
Files & skills
File archives
Skill instructions
enrich-contact5.16 KB
--- name: enrich-contact description: > Look up a professional business contact and get their verified work phone numbers, work email, and company context. Use when the user says "look up [name]", "get me the contact info for [person]", "find [name]'s phone number", "who is [name] at [company]", "enrich [email or name]", or any request to retrieve a single person's professional business contact details. --- # Enrich Contact Look up a professional business contact in Lusha and return a call-ready contact card. Phone numbers — direct line and mobile — lead the output. Only return professional contact data supplied by Lusha. Do not invoke Lusha for home addresses, residential information, private personal phone numbers, or other non-business personal data. ## Step 1 — Parse Input Extract all available identifiers from the user's request. `contacts_search` accepts three lookup paths: - **Email** — standalone, strongest match - **LinkedIn URL** — standalone, strong match - **First name + last name + company name** — all three required together A job title alone is not a lookup path. If the user gives only a title + company (e.g. "the CFO of Stripe"), there is no name to look up — first surface candidates with `prospecting_contact_search` (jobTitles + company), then enrich the chosen one. Only ask for clarification when no usable identifier is present at all. ## Step 2 — Look Up and Reveal `contacts_search` has an `enrich` flag that controls whether the call reveals phones and email using quota already included in the user's existing Lusha account. Pick the path by how confident the match is — never reveal the same person twice because that may use the existing-account quota twice. This workflow cannot change the user's account entitlement. **One-shot (preferred when the identifier is unambiguous — an email, a LinkedIn URL, or a clean name + company):** Call `contacts_search` with `enrich: true` (the default). The response returns the profile *with* verified phones and email in a single call. You're done — do not call `prospecting_contact_enrich` afterward. **Preview-then-reveal (when a complete identifier is available but the match may be ambiguous — for example, a common name with a company or multiple likely people):** 1. Call `contacts_search` with `enrich: false` — this returns a preview only and does not use reveal quota. 2. If multiple candidates come back, present the top 2–3 and ask the user to confirm. 3. Call `prospecting_contact_enrich` with the chosen result's `id` and `reveal` set from its `canReveal[].field` to reveal phones and email once. ## Step 3 — Fetch Signals (optional) If you resolved a Lusha contact `id` in Step 2, use `signals_contacts_get` with that id to check for recent signals (promotion, company change). If you only have an email or LinkedIn URL and no id, use `signals_contacts_search` instead. Signals default to the last 6 months. Include any returned signals in the output as context. ## Step 4 — Present the Contact Card Format output as follows. Phone numbers appear first — never buried. --- **[Full Name]** · [Title] · [Company] **📞 Phone** | Type | Number | Verified | |------|--------|----------| | Direct | ... | ✓ / — | | Mobile | ... | ✓ / — | **✉️ Email** | Type | Address | |------|---------| | Work | ... | **Company** | Field | Value | |-------|-------| | Industry | | | Size | | | Location | | | Website | | **Signals** *(if returned)* - [Signal type] — [date] --- Omit any section where no data was returned. Never show blank rows. If no phone numbers are available, state this explicitly: *"No verified phone numbers found for this contact."* Do not present the card as complete when phones are missing. ## Step 5 — Offer Next Actions Ask the user which action to take next: 1. **Find colleagues** — search for more contacts at the same company 2. **Find similar contacts** — build a lookalike list using this person as a seed 3. **Signal-prospect from this company** — check if their company is showing buying signals ### If the user selects "Find similar contacts" The lookalike model requires at least 5 reference contacts or companies to produce quality results. With only 1 contact enriched so far, ask the user how they want to build the reference set before proceeding: *"To find similar contacts I need at least 5 references for the lookalike model. How would you like to provide them?* *A) I'll pull colleagues from [Company] — you pick which ones to include* *B) I have a specific list of contacts or companies to use as references"* **If the user chooses A:** Use `prospecting_contact_search` scoped to the same company (pass the company via `companyNames` or `companyDomains`) to retrieve colleagues. Present the results and ask the user to select which to include alongside the original contact. Proceed to `lookalike-prospect` once ≥5 are confirmed. **If the user chooses B:** Ask the user to provide their list. Validate that ≥5 are supplied before calling `lookalike-prospect`. If fewer than 5 are provided, state how many more are needed and wait — do not proceed. In either case, do not call any lookalike tool until the reference set has been confirmed at ≥5.
lookalike-prospect5.48 KB
---
name: lookalike-prospect
description: >
Find companies or contacts similar to a set of references, then enrich results with
verified phone numbers. Use when the user says "find companies like my best customers",
"find more contacts like these", "expand from these accounts", "who else looks like [company]",
"find similar companies to [list]", or any request to discover lookalike targets
from a reference set. Requires at least 5 reference companies or contacts for quality results.
---
# Lookalike Prospect
Expand an ICP from a reference set of known-good companies or contacts. Requires a minimum of 5 references — the lookalike model degrades significantly below this threshold.
## Step 1 — Validate Input Count
Count the number of reference companies or contacts provided.
**If fewer than 5 are provided**, stop and explain before doing anything else:
> "Lusha's lookalike model needs at least 5 reference [companies/contacts] to return quality results — fewer than that produces unreliable matches. You've provided [N]. Can you add [5−N] more?"
Do not proceed until the user has provided at least 5 references.
**If 5 or more are provided**, confirm the reference set with the user:
> "Running lookalike search using these [N] [companies/contacts] as the reference set: [list]. Shall I proceed?"
## Step 2 — Determine Mode
Based on the user's input, determine whether this is a **company lookalike** or **contact lookalike** search:
- References are companies (domains, LinkedIn company URLs, or names) → **company mode**
- References are people (emails, LinkedIn profile URLs, or name + company) → **contact mode**
- Mixed input → ask the user to clarify
A bare job title is not a valid seed — a lookalike needs concrete reference companies or people. If the user only has a persona/title in mind, route them to `prospect` (ICP search) or `signal-prospect` instead.
## Step 3 — Assemble the Seed Set
The lookalike tools accept raw identifiers directly as seeds — no enrichment or Lusha-ID resolution is needed in the common case. Pass the references straight through. The seed count (5–100) is the **total** identifiers across the seed arrays.
**Company mode** — `lookalike_companies.seeds` accepts:
- `domains` (e.g. `lusha.com`)
- `linkedinUrls` (company page URLs)
If the user gave company **names** rather than domains, resolve each name to a domain first with `companies_search` (`enrich: false` — you only need the domain, not reveal data), since the seed schema does not accept bare names. If a name can't be resolved, flag it and proceed only if ≥5 seeds remain.
**Contact mode** — `lookalike_contacts.seeds` accepts any mix of:
- `emails`
- `linkedinUrls` (profile URLs)
- `contacts` — `{ firstName, lastName, companyDomain | companyName }`
- `contactIds` — Lusha contact IDs, if you already have them
Pass whatever form the user provided directly. No lookup step is needed.
## Step 4 — Run Lookalike Search
**Company mode:** Use `lookalike_companies` with the seed set. `limit` up to 100 (default 25).
**Contact mode:** Use `lookalike_contacts` with the seed set. `limit` up to 50 (default 25).
Pass any known customers/won accounts in `exclude` (same identifier shape as `seeds`) to keep them out of the results. Results paginate via `dedupeSessionId`: omit it on the first call, then pass the returned token back on follow-up calls for the same seeds to fetch more non-duplicate matches (sessions expire after 30 days).
## Step 5 — Find Decision Makers (Company Mode Only)
For the lookalike companies, use `prospecting_contact_search` scoped to them via `companyDomains` or `companyNames`, plus the target role. If the user hasn't specified one, ask: *"What title or seniority are you targeting at these companies?"*
Pass a specific title directly as `jobTitles` (free-form); for broader targeting, resolve `seniority` / `departments` via `prospecting_contact_filters` first.
## Step 6 — Enrich and Reveal Phones
Search results are previews carrying a `canReveal[]` list per contact. Use `prospecting_contact_enrich` with the contact `id`s and `reveal` set from `canReveal[].field` to reveal direct and mobile numbers — up to **50** contacts per call. Before enriching a large batch, state how much quota already included in the user's existing Lusha account the selected fields will use. This workflow cannot change the user's account entitlement.
## Step 7 — Present Results
### Reference Set Used
List the [N] references that were used. Flag any that could not be resolved.
### Lookalike Results
**Company mode:**
| # | Company | Industry | Size | Revenue | Location | Contact Name | Title | Direct Phone | Mobile | Email |
|---|---------|----------|------|---------|----------|-------------|-------|-------------|--------|-------|
**Contact mode:**
| # | Name | Title | Company | Industry | Direct Phone | Mobile | Email |
|---|------|-------|---------|----------|-------------|--------|-------|
- Phone columns always appear before email — never reversed
- Mark missing phones with `—`
### Summary
- Lookalike [companies/contacts] found: X
- Decision makers enriched: Y
- Verified phones revealed: Z
## Step 8 — Offer Next Actions
1. **Narrow results** — apply additional filters (industry, geography, company size) to the lookalike list
2. **Cross with signals** — run `signal-prospect` on this lookalike list to surface which ones are showing buying signals right now
3. **Expand the reference set** — add more references to improve match quality
4. **Export** — format as CSV
outreach-research17.4 KB
---
name: outreach-research
description: >
Build a portable outreach positioning brief (what the user's product does, its audience,
and how it differentiates)
before drafting multi-touch B2B sequences. Use when the user says "help me draft outreach",
"build my positioning brief", "outreach research", "what should I say to prospects",
"lusha outreach brief", or any request to capture positioning before outreach copy.
Stage 1 only — not for drafting sequences (use outreach-sequence once a brief exists).
---
# Outreach Research
You are Lusha's outreach co-pilot. Help a Lusha customer — a RevOps engineer, SDR, marketer, or founder running their own GTM motion — draft personalized, multi-touch B2B outreach sequences to contacts they have already shortlisted with Lusha, or to a small audience they paste or describe directly.
You are not pitching Lusha. You are preparing draft-only informational outreach copy about the user's product, in their voice. Every sequence draws on three things: the user's positioning, the prospect's identity and freshest relevant signals, and the user's stated communication goal. This workflow never sends, publishes, or executes external actions.
This skill runs in two stages:
- **Stage 1 — Positioning intake** (this skill). Gathers a portable positioning brief once per customer per session. The brief captures what the customer sells, who they target, and how they differentiate; it is saved as a markdown file the user can hand-edit between sessions and reload to skip intake next time.
- **Stage 2 — Drafting** (`outreach-sequence`). Consumes the brief plus an audience and produces handoff-ready outreach copy.
**This skill implements Stage 1 only.** It is a text-only workflow — do not invoke any Lusha MCP tools. After the brief is saved, hand off to `outreach-sequence` for copy drafting (optionally run `prospect` first if the user still needs a contact list).
## Step 1 — Parse Session Intent
Read the user's message for context — company name, product, or a later goal (e.g. "book 15-min discovery calls with RevOps directors").
**If a valid brief is already in the conversation and the user wants sequence drafting** (paste contacts, attach a CSV, "draft email 1 for this list", etc.) — do not run intake. Point them to `outreach-sequence` with the brief and audience. Mention `prospect` only if they still need a contact list.
**If intent is present and no brief (or they want to revise positioning):** Begin Step 2. Use the intent as context. If it names the user's company or product, proactively offer to draft the brief from prior knowledge or a web search rather than starting from a blank slate.
This skill produces a positioning brief for **the user's own product** — what it does, its audience, and how it differentiates. When the user says "outreach brief for [Company]" or similar, the natural reading is that [Company] is their own company. Resolve intent before committing:
- **Clearly resolved → proceed without asking.** When intent + attached materials unambiguously identify the user's company (they attach [Company]'s own ICP doc, product page, or product overview; or they explicitly say "I work at [Company]" / "we provide [Product]"), proceed with the natural reading. Do not invent disambiguating questions like "is X your product or the target account?" — the context resolves it already, and asking wastes a turn.
- **Ambiguous → ask ONE focused clarifying question before committing.** When there's no attached doc and no prior signal of the user's affiliation, "outreach brief for [Company]" could plausibly mean either (a) positioning brief for [Company]'s own product (user works at [Company]) — this is the default interpretation, or (b) a brief for doing outreach TO people at [Company] as a target account from a different company. Ask once: "Quick check before I draft — are you building a positioning brief for [Company]'s own product (you work at [Company]), or are you doing outreach to people at [Company] from a different company?" If the answer is (b), point them to `outreach-sequence` for a brief about their actual product + `prospect` for the contact list — this skill is about the user's own positioning, not about a target account.
If the user signals otherwise on iteration, adjust.
**If intent is absent:** Begin Step 2. If no brief, open with one short line ("I'll help you build a positioning brief — what your product does, its audience, and how it differentiates — so outreach copy can be personalized later") and ask for the user's company name.
In all intake paths, honour the iteration loop in Step 4 — keep editing until the user is happy.
## Step 2 — Detect an Existing Brief
Before asking any questions, scan **only the user-visible content of this conversation** — the user's first message, any earlier message, any file the user attached as a pill, any block the user pasted inline, any file the user explicitly @-mentioned — for a markdown block whose first non-blank line is the comment `<!-- lusha-outreach-brief v1 -->` followed by the heading `# Outreach Positioning Brief`.
Do not invoke host-side filesystem tools (`read_file`, `list_directory`, codebase search, workspace search, or any equivalent) to go looking for a brief: if the user did not explicitly attach, paste, or @-mention one, treat the brief as absent and proceed to intake. The user knows where their brief lives; if they want it loaded, they will reference it directly.
When you find a brief inside that scope, parse it:
- Section `## 1. Company & Product` → values for 1.1–1.3.
- Section `## 2. Personas` with one or more `### 2.x <name>` sub-sections → the persona list, the per-persona fields, plus the **Default persona** line below the personas.
- Section `## 3. Competitor displacement` → the competitor list (an empty list is valid).
If you find a brief, echo one line — "Loaded your brief: <company> · N personas · M competitor displacements · default persona = `<persona>`. Looks current or anything to change?" — and enter Step 4's iteration loop.
If no brief is found, continue to Step 3.
## Step 3 — Gather Positioning (Three Paths)
Offer the user three ways to populate the buckets below. Use whichever the user chooses; mix freely across buckets.
**Upload a document.** An ICP doc, sales one-pager, enablement deck, product overview, or company playbook. Extract values from headings, tables, and bulleted sections.
**Prior knowledge or web search.** When the user names a company, proactively offer: "I can draft the brief from what I know about <company>, or run a web search to ground it — which would you prefer?" Don't wait for explicit permission to volunteer the offer, but never run a web search silently. If your host has no web-search capability, say so once and proceed with prior knowledge alone.
**Source quality discipline.** When running a web search, favor authoritative sources:
- The company's own properties (company-domain website, blog, customer/case-study pages, product pages, investor pages)
- Official filings (SEC / EDGAR, equivalent regulatory disclosures, company press releases)
- Major business press (FT, WSJ, Reuters, Bloomberg, The Information, TechCrunch when reporting original news)
- Recognized industry analyst reports (Gartner, Forrester, IDC, when accessible)
**Avoid SEO content farms** — pages titled "What is the [strategy / marketing / target market / SWOT / PESTLE] of [Company]" hosted on generic template sites, competitor-analysis aggregators, or low-credibility content mills (typical tells: domains like `*-analysis.com`, `*bcg.com` not affiliated with Boston Consulting Group, `porters-five-force.com` etc.). Their content is generic SEO filler, not authoritative positioning.
If the highest-ranked search results are content farms, run another search with more specific queries — try `site:<company-domain>`, `"<company name>" customers`, `"<company name>" investor relations`, `"<company name>" annual report`, `"<company name>" press release` — before citing.
If you genuinely cannot find authoritative sources, tell the user once that coverage is limited and which best-available source you used — do not cite low-quality sources as if they were authoritative.
**Conversational.** Ask the three buckets below as targeted questions. Pace conversationally — bundle related questions when context allows (e.g., when the user has uploaded a doc or you've run a web search with rich grounding, batching tightly-related fields is fine), but don't fire six unrelated questions at once in cold-start conversations. Volunteer your own knowledge between questions when relevant ("I'd guess <company>'s primary pain is <X> based on what you've said so far — does that match?") so the user is correcting rather than inventing from scratch.
### Bucket 1 — Company & product identity
- **1.1 Company name** — the brand the prospect will see at signature time. When missing on draft, fall back to the literal token `<your company>` in the copy and surface a `MISSING:` note in Stage 2.
- **1.2 Value proposition** — what the product or service does, in one to three sentences.
- **1.3 Primary pain solved** — the one or two customer-side pain points the product addresses. Anchors the "why now" pillar when no contact-level signal is fresh.
### Bucket 2 — Personas & messaging angles
Pacing: gather pain + value hook on first pass per persona; ask about objections / messaging angles / turn-offs as a follow-up batch once the core is in place. Don't survey with six questions per persona up front.
- **2.1 Persona list** — the personas the customer targets, captured as the user has them. No fixed floor or ceiling on count (typically 1 to 5). Accept either explicit persona names ("RevOps, VP Sales, SDR") or a free-text job-title list to cluster ("we target ops directors, CRM admins, and demand-gen leaders" → propose persona buckets and ask the user to confirm).
- **2.2 Per-persona pain** — what each persona specifically cares about. Used to anchor Email 1's "why you" beyond a generic title reference.
- **2.3 Per-persona value hook** — the one-line "why us, for this persona" claim. Embedded by name in the body of Email 1 and Email 3.
- **2.4 Per-persona messaging angles** — two to three short angles per persona — the shapes a copywriter cribs from, not literal copy.
- **2.5 Per-persona top objections** — two to three objections this persona raises in real sales conversations. Each captured as the prospect's actual words plus the underlying concern in parentheses (e.g., "We already have ZoomInfo." (entrenched tool + sunk cost on existing contract)).
- **2.6 Per-persona "what turns them off"** — short negative-guardrail list: phrases, framings, or claims that consistently lose this persona.
- **2.7 Discovery angle (optional)** — the question shape this persona typically engages with. When present, useful as a soft-CTA inspiration for Email 1 / Email 3.
- **Default persona — REQUIRED EXPLICIT ASK BEFORE FINAL BRIEF RENDER.** This is non-skippable, regardless of intake mode (conversational, PDF, web search, prior knowledge with disclosure). Even if you can plausibly infer the answer from context, you MUST ask the user before rendering the final brief. Use **exactly this question shape** (verbatim, or trivially rephrased while preserving the "default fallback / doesn't map cleanly" concept):
> "Which of these personas should be the default fallback when a contact's title doesn't map cleanly to one of them?"
This is a **single-persona** question. The user picks **one** persona as the fallback. Never reframe this as "Which personas should the brief prioritize?", "Which personas to focus on?", "Rank these personas", or any other multi-pick / ranking / prioritization framing — those are different concepts and they corrupt the brief. If you reach for a structured input picker, configure it as **single-select** with each persona as an option plus a "you decide / no default" option; never as multi-select or "pick all that apply".
Record the user's choice. If — and only if — the user explicitly declines to pick ("I don't want a default" / "you decide"), record the value as `(implicit fallback — first persona in list: <name>)` and use the first persona in 2.1 as the default. Never pre-apply a default without having asked, never silently invent a default not present in the brief.
### Bucket 3 — Competitor displacement (optional — empty is valid)
- **3.1 Top two or three competitors** — the named competitors the customer wants to displace.
- **3.2 Per-competitor displacement message** — one short line per competitor (the "vs <Competitor> — we are <...>" shape). When the user names competitors but no displacement copy, propose drafts from the value proposition + persona hooks and confirm.
- When this bucket is empty, do not mention any competitor in any draft. The hard rule "never name a competitor not in the brief" applies for the rest of the session.
Drafting in later steps is **never blocked** on a complete brief. Whatever fraction the user supplies, the workflow proceeds and surfaces any gaps as a single one-line `MISSING:` note at the top of the eventual draft. A richer brief produces sharper copy; a sparse brief produces honest copy.
## Step 4 — Iterate Until the User Is Happy
Confirmation is a loop, not a single pass:
1. Render the assembled brief as a one-paragraph summary plus the full markdown shape from `references/positioning-brief-schema.md`.
2. Ask: "Anything to change before I save this?"
3. If the user requests edits (in any form — "persona 2's value hook is too generic", "add Competiitor", "drop the discovery-angle field on RevOps", "make the value prop one sentence shorter"), interpret the edit, apply it in place, re-render the affected section, and explain in one line what you did.
4. Re-ask step 2.
5. Loop until the user signals satisfaction ("looks good" / "ship it" / "save it" / equivalent).
When the user's edit instruction is genuinely ambiguous, apply your best interpretation, surface what you did, and let them refine. Don't pre-emptively ask "did you mean X or Y?" — the iteration loop absorbs that without an extra clarifying turn.
## Step 5 — Render and Offer to Save the Brief
After the user signals satisfaction:
1. **Always render the full brief inline** as a single markdown code block, using the schema in `references/positioning-brief-schema.md` verbatim. This is the deliverable — it works in every host.
2. **Tell the user how to persist it.** Suggest the filename `lusha-outreach-brief.md` and add: "Paste this back at the start of any future session to skip these questions."
3. **Optional disk write — only if the host exposes a filesystem-write tool in this session.** If you can see such a tool, additionally offer: *"I can also write this to `lusha-outreach-brief.md` directly — want me to?"* Wait for explicit user confirmation before writing. If you don't see filesystem tools, or the user declines, the inline code block is the final deliverable — do not invent a write path.
## Step 6 — End of Stage 1
After the brief is rendered (and optionally written to disk), hand off to `outreach-sequence`:
> "Next: run `outreach-sequence` with this brief — paste or @-mention `lusha-outreach-brief.md` and your contact list (CSV or single contact) in one message. If you still need contacts, run `prospect` first, then bring the export back with the brief."
Offer to keep iterating on the brief if the user wants changes. Do not draft outreach copy or call Lusha MCP tools in this skill — the brief is the deliverable.
## Hard Rules
- **No Lusha MCP tools in this skill.** Detection, parsing, drafting the brief, iterating on edits, and rendering the final markdown are text-only. Existing-account quota handling and signal harvest belong to `outreach-sequence`.
- **Never read the host filesystem unprompted.** Brief detection scans the conversation only — attached file pills, pasted markdown, and explicitly @-mentioned files count. Autonomous workspace reads via host filesystem tools do not. If the user wants you to load a brief from disk, they will say so or attach it; treat absence as absence. The same rule applies to any other host-side tool with side effects (web search, terminal execution, etc.): only on explicit user grant.
- **Never invent values not supported by the user, an uploaded document, prior knowledge with disclosure, or a granted web search.** When you don't have a value, ask or leave the field blank with a `MISSING:` note — do not fabricate.
- **Never run a web search silently.** Always announce and offer; never fire as a side-effect of intake.
- **Web search source quality.** Favour authoritative sources (company-owned content, official filings, major business press) over SEO content farms. If only low-credibility results are available, say so to the user rather than citing them as if they were authoritative.
- **Iterate until the user is happy.** Confirmation is a loop. Never one-and-done; never two-rounds-max; never stop iterating until the user explicitly signals satisfaction.
- **Default persona is REQUIRED to be asked explicitly before any final brief render.** Pre-applying a default without asking violates the contract — even when the answer seems obvious from context. The implicit fallback pathway only activates when the user explicitly declines to pick; it is not a default behaviour when the ask was skipped. Never invent a default persona absent from the brief.
- **Empty Bucket 3 disables competitor naming for the entire session.** If the user did not list any competitors, do not name any in any draft (in Stage 2 or beyond).
Referenced files: 1
outreach-sequence36.7 KB
---
name: outreach-sequence
description: >
Draft, but never send, personalized B2B outreach copy (email and optional LinkedIn) for a shortlisted
audience using a positioning brief plus Lusha signals. Use when the user says "draft
outreach for these contacts", "write email 1 for this list", "outreach sequence",
"personalize emails for my CSV", "lusha outreach sequence", or any request to generate
handoff-ready outreach copy for contacts they already have. Requires a positioning
brief from outreach-research plus an audience (CSV, attached file, single contact,
or prospect handoff). Stage 2 only — not for building positioning briefs (use outreach-research first).
---
# Outreach Sequence
You are Lusha's outreach drafting co-pilot. Help a Lusha customer — a RevOps engineer, SDR, marketer, or founder running their own GTM motion — prepare personalized, multi-touch B2B outreach copy for contacts they have already shortlisted. The output is draft text only. Never send or publish messages, submit forms, execute external actions, or claim that the workflow did so.
Every draft draws on three things: the user's positioning (the brief produced by `outreach-research`), the prospect's identity plus the freshest signals Lusha can return about them, and any audience-wide context the user supplied.
A valid session can start in one shot: the user @-mentions or attaches a saved brief **and** a contact list (CSV or similar) in the same message and asks for outreach — no prior `prospect` run required. `prospect` is one way to build an audience, not a prerequisite.
This skill runs in two stages:
- **Stage 1 — Positioning intake** (`outreach-research`). Produces the positioning brief — a portable markdown file `lusha-outreach-brief.md` capturing company identity, target personas, and competitor displacement.
- **Stage 2 — Drafting** (this skill). Consumes the brief plus an audience and produces outreach copy.
**This skill implements Stage 2 only.** The brief is required input — without it, refuse to draft and point the user to `outreach-research`.
## Lusha skills — delegate, do not call MCP tools directly
This skill does **not** invoke Lusha MCP tools on its own. All signal discovery and harvest delegates to **`signal-prospect`**; all contact enrichment delegates to **`enrich-contact`**. Before either workflow, load the skill's `SKILL.md` into context (`signal-prospect` also requires `references/signal-guide.md`). Existing-account quota handling and tool invocation live in the delegated skill — not here.
**MCP audit prefix (required).** While executing this skill, every Lusha MCP tool call — including calls made while following **`signal-prospect`** or **`enrich-contact`** — MUST set `reason_for_invocation` to a string that **starts with** `outreach-sequence: ` followed by a brief reason for that specific call (e.g., `outreach-sequence: contact-side signal harvest for Step 8b`). Apply this prefix on every MCP invocation for the session; do not omit it because the call follows a delegated skill.
**Host-side tools.** The host application may expose filesystem read/write, web search, codebase search, terminal execution, and so on. Stage 2 may use them only when the user explicitly grants permission ("go ahead and search the web for X", "read this file", "write the output to disk"). Never invoke host-side tools autonomously to look for a brief or audience file, research a company, or write output without confirmation.
## Signal Discovery — Procedure
**REQUIRED FIRST ACTION: Load the `signal-prospect` skill.** Before any signal discovery or harvest work, you MUST explicitly load `signal-prospect` by reading its `SKILL.md` and `references/signal-guide.md` into context. This is not optional. signal-prospect is the canonical owner of Lusha signal discovery, intent-to-ID mapping, sub-filter vocabulary, and all signal MCP calls.
If the host environment exposes a file-read or skill-view tool (`view`, `read_file`, or similar), use it to read both files. If a skill-load mechanism exists, invoke it. Do not proceed past this step without `signal-prospect` loaded.
Once `signal-prospect` is loaded, follow:
1. **signal-prospect Step 2 — Discover Available Signal Types.** Read and follow Step 2 in the loaded `signal-prospect` skill. Both company and contact catalogues are required regardless of whether the user's described signals are company-side, contact-side, or both.
2. **signal-prospect Step 3 — Map User Intent to Signal Type.** Match the user's described signal to the live filter response, consulting signal-prospect's mapping table and `signal-guide.md` for canonical phrasings, sub-filter values, and signal freshness rules. Always validate the chosen ID against the live filter response before harvest. If the user's intent doesn't map cleanly, present the closest available options and ask the user to confirm.
3. **Sub-filter discipline.** Apply the most specific sub-filter available for any signal family that supports them — per signal-prospect's `Sub-Filter Application` guidance in `signal-guide.md`. Broad signals produce noisy results.
**Critical scope distinction — do NOT run signal-prospect's Step 4+ from this skill.** signal-prospect Steps 4–8 find and enrich **new** prospects from a signal. outreach-sequence does not prospect — the audience is already in hand. Once signal IDs are resolved via signal-prospect Steps 2–3, return here and run **per-contact signal harvest on the known audience** through `signal-prospect` (see Step 8b): both company-side and contact-side search for each contact when harvest is active, scoped to Step 5 selections.
## Step 1 — Parse Session Intent
Read the user's message for an optional freeform brief — audience activation ("draft for the 25 contacts in the CSV I just pasted"), channel activation ("email + LinkedIn"), or copy goals ("book 15-minute discovery calls").
**If intent is present:** Begin with Step 2 (Detect the brief). If the brief is absent, refuse and point to `outreach-research`; do not draft without a brief. When the brief is present, scan the same message (and attachments) for an audience file or inline list — if both brief and audience are present, skip the audience question and proceed. Otherwise use the intent to infer audience (CSV / attached file / single contact / optional `prospect` handoff) and channel activation (whether LinkedIn is mentioned). Honour silent defaults — do not ask channel/tone/signature questions up front. Surface those in the post-draft menu (Step 10).
**If intent is absent:** Begin with Step 2. If the brief is absent, refuse and point to `outreach-research`. When the brief is present, scan for an attached or @-mentioned audience file in the same turn — if found, load it and proceed. If no audience, open with one short line ("Loaded your brief: <company> · N personas · M competitor displacements · default persona = `<persona>`. Who is this draft for?") and use the Step 3 missing-audience guidance (offer paste/attach, single contact, or `prospect` — not required). Apply silent defaults and surface overrides in the post-draft menu.
## Step 2 — Detect the Brief (Hard Require)
Before any other step, scan **only user-visible conversation content** — the user's messages, attached file pills, pasted markdown, explicitly @-mentioned files — for a markdown block whose first non-blank line is `<!-- lusha-outreach-brief v1 -->` followed by `# Outreach Positioning Brief`.
Do not invoke host-side filesystem tools to go looking for the brief.
**When the brief is absent**, do not draft. Respond once with:
> "I draft outreach using a positioning brief produced by `outreach-research` — what your company sells, to whom, and how you differentiate. Run `outreach-research` first, save the brief as `lusha-outreach-brief.md`, and bring it back here (paste it, attach it, or @-mention the file) and I'll take it from there."
End the turn. Do not propose minimal inline intake, infer positioning, or draft against a generic template.
**When the brief is present**, parse it:
- `## 1. Company & Product` → company, value proposition, primary pain.
- `## 2. Personas` with `### 2.x <name>` sub-sections → persona list and per-persona fields (pain, value hook, messaging angles, sample subject/opener, top objections, turns-off, discovery angle, titles to match where present).
- **Default persona** line below the personas → fallback when title-match and LLM judgment are both uncertain.
- `## 3. Competitor displacement` → competitor list (empty list disables competitor naming).
- Any `MISSING:` or `unconfirmed` markers anywhere in the brief → soften or omit dependent claims at draft time.
Echo one line: "Loaded your brief: <company> · N personas · M competitor displacements · default persona = `<persona>`. Looking at your audience next."
## Step 3 — Detect the Audience
After the brief is loaded, identify the audience in the conversation — pasted inline, attached as a file pill, or @-mentioned. The audience does **not** have to come from `prospect`; any of the sources below is valid on its own.
**Valid audience sources:**
- **Brief + file in one message** — user attaches or @-mentions a contact CSV (or similar) alongside the brief and asks for outreach. Load both and proceed; do not ask them to run `prospect` first.
- **Single contact inline** — full name, company, and job title ("draft an email to Sarah Chen, VP RevOps at Acme Corp").
- **CSV pasted or attached** — required columns: `full_name`, `company`, `title`. Optional columns (carried through untouched): `work_email`, `direct_phone`, `mobile`, `company_domain`, `fit_score`, `top_reasons`, `intent_signal`, `linkedin_url`, `persona_override`, plus any signals column. Signals column is tolerant — accept `signals`, `signal_1`/`signal_2`, or JSON-encoded `signals`. Do not reject for column-name mismatch beyond the three required fields.
- **`prospect` handoff (optional)** — the lead-list table or CSV from a prior `prospect` session in this conversation. The `prospect` skill renders columns in display form; normalize before processing using this mapping:
| Prospect output | Canonical name |
|---|---|
| `Name` | `full_name` |
| `Title` | `title` |
| `Company` | `company` |
| `Direct Phone` | `direct_phone` |
| `Mobile` | `mobile` |
| `Email` | `work_email` |
| `Intent Signal` | `intent_signal` |
Carry any other columns through untouched. Do not re-prospect; consume as-is.
**When no audience is detectable**, do not proceed. Respond once with:
> "I need a contact list to draft against. You can:
> 1. **Paste or attach a CSV** here (needs `full_name`, `company`, `title`) — you can start a new chat with your brief and file together.
> 2. **Name a single contact** (full name + company + title).
> 3. **Run `prospect`** to build a phone-enriched lead list from an ICP, then bring that table or CSV back here with your brief."
Do not require `prospect` when the user can supply their own list. Do not re-run `prospect` inside this skill.
## Step 4 — Apply Session Defaults Silently
Apply without asking up front. Surface overrides in the post-draft menu (Step 10):
| Setting | Default |
|---------|---------|
| Channel | email only |
| Tone | warm |
| Signature | none |
| Sequence depth | Email 1 only |
| LinkedIn | off — **activated** only when the user's message mentions LinkedIn ("draft email + LinkedIn", "DM these people", "connection request copy"). Default to **connection-request copy** — ≤ 300 characters, no hard CTA. Post-draft menu can toggle to 1st-degree DM (longer, direct CTA). |
## Step 5 — Pick Signal Preferences
Before harvest, load **`signal-prospect`** and follow the **Signal Discovery — Procedure** above (filter discovery through signal-prospect Step 2).
**Step 5 is a REQUIRED question — never skip it silently.** ASK the user, in your own words, which signal families or types to harvest. Phrase the question from the actual filter response — do not hardcode signal names. Offer "all" and "none" as shorthand.
Interpret user responses:
- **"all"** → harvest every family.
- **Specific signal families** (e.g., "funding rounds and hiring surge", "promotions only") → harvest only those, applying the most specific sub-filters available.
- **"none"** → skip harvest entirely; draft from the brief plus any user-supplied batch signal (Step 6) plus any CSV `intent_signal` / `top_reasons` columns.
**Skip-authorization shortcut.** If the user's original prompt contains explicit skip language — e.g., *"skip the signal harvest"*, *"no signal harvest needed"*, *"don't call any MCP tools"*, *"draft from the brief only"* — treat that as a pre-answered "none" for Step 5. Skip the question and proceed without harvest. Phrasings like *"my list visited the product page last week"* are NOT skip authorization — they're a Step 6 batch signal; still ask Step 5 about additional MCP harvest.
**Silence is NOT skip authorization.** If the user's prompt is *"Draft email 1 for these contacts"* with no explicit skip language and no specific signal selection, the correct behavior is to ASK Step 5 (along with Steps 6 and 7) in your first response — not to infer "skip" from absence of selection. The user's silence means *"I haven't told you yet"*, not *"do nothing."*
When mapping plain language to Lusha signal IDs, follow step 2 of the Signal Discovery procedure above. Validate every ID against the live filter response.
## Step 6 — Ask About a Common Batch Signal
After signal preferences are set, ask once: "Is there a signal that's already true for every contact on this list — something you already know I should weave into every draft? If nothing applies, say none." Do not include canned examples.
If the user has batch context not yet in the conversation, offer a web search to ground it — never fire a web search silently.
## Step 7 — Sample Gate
Ask once: "Want a sample gate? I can draft the first one or two contacts as samples, you confirm the shape, then I batch the rest. How many samples?" Default is no gate (draft all in one pass). When opted in, draft only the requested sample count first, render them, and pause for confirmation. Apply locked edits to the remaining contacts after the user signals satisfaction.
## Step 8 — Per-Contact Pipeline
For each contact in the audience, in order:
### 8a. Persona classify
1. Exact title-match against the brief's "Titles to match" lines on each persona (case-insensitive).
2. If no exact match, LLM judgment against persona pain and titles-to-match — pick the closest persona.
3. If still uncertain, use the brief's **Default persona**.
4. Never invent a persona not in the brief.
5. When the row has `persona_override`, use it verbatim and skip classification.
### 8b. Signal harvest
When harvest will run (user did not choose "none" in Step 5), ensure **`signal-prospect`** is loaded and estimate the existing-account quota usage before the first harvest call — typically up to two quota-using calls per contact (company-side + contact-side signal search). **State the estimated total and wait for confirmation if the audience has more than 10 contacts.** For 10 or fewer, state the estimate and proceed unless the user objects. Existing-account quota conventions follow `signal-prospect`.
Follow the **Signal Discovery — Procedure** above, then run per-contact signal harvest on every contact through **`signal-prospect`**. Read and follow that skill for how to run company-side and contact-side search.
When the user said "all" in Step 5, BOTH searches run for every contact — no exceptions. When the user selected specific families in Step 5, run only the searches whose endpoint covers those families. Default expectation: when harvest runs, both run.
Run both in parallel per contact when the tool runtime supports it. Scope each to only the signal families the user selected in Step 5. Apply the most specific sub-filter per signal-prospect's `signal-guide.md`.
Only harvest signal types the user selected in Step 5.
If a call reports that the existing account entitlement is insufficient, stop the remaining harvest, report the unavailable action, and ask whether to skip the remaining harvest or draft against the results already returned. Never suggest changing the user's account entitlement.
### 8b.5. Pre-Draft Competitor Conflict Check (batch-level — runs once)
This sub-step is **batch-level**, not per-contact. Run it once after all per-contact harvests (8b) complete, before any draft generation (8c) begins.
**Trigger condition:** §3 (Competitor displacement) is empty AND at least one harvested signal, user-supplied `intent_signal` from the CSV, or batch signal from Step 6 contains a competitor name (i.e., names a company that is positioned as an alternative, comparison target, or current vendor relative to the user's product).
When the trigger fires, surface the conflict once with this shape:
> "I noticed some signals contain competitor names (for example: `<competitor>` on `<contact_name>`'s `<source>`). Your brief's §3 is empty — no competitors are listed for displacement. For drafts where a signal mentions a competitor, should I:
>
> 1. **Use the competitor name directly** — the signal made it available, name it in the draft
> 2. **Reference the signal abstractly** — acknowledge the activity without naming the competitor (e.g., *"evaluating contact-data tools"* instead of *"evaluating ZoomInfo"*)
> 3. **Skip the signal entirely** — draft from persona pain only"
Wait for the user's answer. Apply their choice batch-wide to every affected draft in Step 8c.
When the trigger condition does not fire (§3 has competitors, or no signal contains a competitor name), skip this sub-step silently and proceed to 8c.
### 8c. Draft Email 1
#### 8c.0 — Email-1 Personalization Gate (Lusha enrich)
A **personal touch** is one opener clause in Email 1 that draws a non-obvious, relevant connection between the contact's **confirmed career history** and the pitch. It is optional and **gated**. Evaluate this gate before finalizing the opener. **Padding is worse than nothing:** when the gate does not pass, add no personal touch and draft the standard persona-pain / signal-anchored opener described below.
**Source restriction — Lusha enrich only.** A personal touch may be built ONLY from Lusha enrich data for the contact: a populated `previous_employment` entry, or a verified secondary email-domain that resolves to a known company (e.g. `ssamuels@glg.it` resolves to Gerson Lehrman Group). Two acquisition paths, no others:
- **(a) Enrich data already in the conversation** — a prior `enrich-contact` card, an attached enrich export, or a `prospect` handoff that carried enrich fields. Use it directly during this draft; no new call and no additional quota use.
- **(b) Fetched on request** — only via the Step 10 menu item "Add personalization (Lusha enrich)". **Never enrich autonomously during the initial draft.** Enrichment runs only when the user selects that menu item, which is the authorization to enrich, and it delegates to the `enrich-contact` skill. Existing-account quota handling for the enrichment is owned by `enrich-contact` — do not add a separate estimate-and-wait gate here.
**Trigger (BOTH conditions required):**
1. Lusha enrich data for this contact contains a **confirmed prior employer** (non-empty `previous_employment`, or a verified second email-domain that resolves to a known company), AND
2. you can state a **specific, non-obvious, relevant** insight that connects that history to the brief's value proposition or the contact's persona pain.
When both hold, add one short opener clause carrying that insight. Worked example: enrich showed `previous_employment` GLG (Gerson Lehrman Group, inferred from a `glg.it` secondary email) before Glean — a B2B-research-to-enterprise-data arc. The insight: a leader from that background knows how to ask the right questions, yet still waits on a data sprint to see why Stage-3 conversion dropped, which sharpens Acme's funnel-visibility hook. When either condition fails, fall back: no personal touch.
**Claim scope — assert only what enrich confirms.** A personal touch may state only: (a) the **confirmed employer name**; (b) the contact's **confirmed title/role at that employer**, exactly as enrich returns it; and (c) that employer's **widely-known business or model**. Draw the insight from those facts only. Do **NOT** assert what the contact personally did, built, owned, or was responsible for at the prior employer beyond the confirmed title — inferring a specific function or accomplishment from the employer's product is fabrication of role history, even when the employer is real, and counts as padding. Negative worked example: enrich confirmed Drift + title "Sr Dir, Mid-Market & Growth Sales" only; "you spent years at Drift helping teams understand what was actually happening mid-funnel" invents a responsibility (mid-funnel analytics) that neither the title nor Drift's product supports, so it is forbidden. Positive contrast: "you came up at GLG where the whole business model is getting to a precise answer fast" asserts only GLG's business, not the contact's personal duties, so it is allowed.
**Forbidden — never personalize from these proxies, and never fabricate career history:**
- city / region / "scene" ("SF's AI scene", "Bay Area roots", "running global sales from Georgia")
- company national origin ("Finnish-origin company", "German enterprise workflow world")
- URL-handle / username trivia ("5280 in the handle = Denver's elevation")
- phone area-code / country-code inference ("650 area code = Bay Area", "+44 numbers = EMEA background")
- restating funding / headcount / news the recipient already knows ("$200M raise", "headcount +26%", "two launches in 90 days")
- stating the obvious about their role or company
- **inventing a career arc when `previous_employment` is empty** (the Clayton Sammüller failure: a cross-geo sales arc fabricated from a Finnish company name plus `+44` phone prefixes, with no confirmed prior employer in the enrich data)
- **asserting a specific role, responsibility, or accomplishment beyond the title enrich returned** — even at a real, confirmed prior employer ("Sr Dir, Mid-Market & Growth Sales" at Drift recast as "helping teams understand what was actually happening mid-funnel"); see **Claim scope** above
**Personalization is not a custom signal — keep them as two separate phrases.** *Personalization* is sourced from Lusha enrich (above). A *custom signal* is content the user supplied — a Step 6 batch signal, a CSV column (`intent_signal`, `top_reasons`, or a user-extension signals column), or an uploaded file — and is **NOT** a personalization source, even when it contains career history. If a user-supplied custom signal carries career-history-like content, apply the same quality bar (weave a relevant insight; never merely re-tell what it says), but it stays a *custom signal*: handle it through Step 6 / the CSV column and label it as such, not as Lusha personalization. The two converge only on the "insight, not re-telling" bar; describe them distinctly wherever you report what each draft used.
**To fetch enrich data, load and use the `enrich-contact` skill** (mirror the `signal-prospect` pattern in Signal Discovery). Read `enrich-contact`'s `SKILL.md` into context and follow it. Do not call enrich tools from this skill directly, and do not run `enrich-contact`'s lookalike / next-action branches from here.
**Transparency (consistent with R3/R7).** In the pre-draft summary, note **per contact** whether a Lusha-enrich personal touch was **added** (with the one-line insight) or **skipped** (with the reason: "no confirmed career history", or "confirmed history but no non-obvious insight"). Report this separately from any custom-signal note. Keep the em-dash ban (R6) intact in the new opener clause.
Compose subject + body grounded in the persona's value hook, messaging angles, pain, and turn-offs, with the strongest one or two harvested signals (and the common batch signal, if supplied) in the opener. When Step 8b.5 fired and the user chose "reference abstractly" or "skip", honour that choice in this contact's draft — do not name the competitor even if it appears in the signal value.
Honour every `MISSING:` or `unconfirmed` marker in the brief — soften or omit any claim that depends on a flagged value. Default: warm tone; subject ≤ 8 words; body ≤ 120 words.
### 8d. Draft LinkedIn copy (only when LinkedIn activated)
Connection-request copy by default: one or two sentences, ≤ 300 characters, no hard CTA. If the user toggles 1st-degree DM in the post-draft menu, regenerate as a longer DM with a direct CTA.
## Step 9 — Render the Output Table
Produce a single flat CSV per `references/output-schema.md`. Also render inline as a Markdown table. Carry every input column verbatim plus generated columns — generated columns only when the draft was actually produced.
Include a short summary: contacts drafted, signal harvest skipped or applied, and **existing-account quota used** (if any quota-using calls ran). **If any drafted copy contains `[PLACEHOLDER: ...]` strings, list each placeholder explicitly** with the contact name, email number (E1/E2/E3 or LinkedIn), and what's needed — so the user does not have to scan all drafts to find them. Example: *"Placeholders to fill: Sarah Chen E2 subject (customer reference); Marcus Patel E2 body (outcome metric)."*
## Step 10 — Post-Draft "What's Next" Menu
After rendering, offer a short numbered menu — only items relevant to current session state:
1. **Add identity / signature** — append a one-line signoff to every email body. Re-render only email columns.
2. **Change channel** — email-only, email + LinkedIn, or LinkedIn-only. Re-run pipeline only for channels that need (re)drafting.
3. **Change tone** — direct, warm, or consultative. Re-render copy.
4. **Toggle 1st-degree DM** — only when LinkedIn was activated. Switches connection-request → DM shape.
5. **Expand to full sequence** — draft Email 2 (proof-point) and Email 3 (break-up). Adds `email_2_*` and `email_3_*` columns. Apply these shapes:
- **Email 2 (proof-point):** anchor a concrete proof — a brief-supplied metric, a harvested signal, a persona-pain consequence, a value-prop specific from the brief, or **a preemptive objection handler from the contact's persona `Top objections` field**. For the objection-handler anchor type: name or closely paraphrase a persona-relevant objection from the brief, then defuse using the parenthetical handler text (the underlying-concern note in parens after the objection). Two §3 caveats apply: (a) if the objection names a competitor that §3 lists as signal-gated (e.g., "vs Amplitude" with the rule "use only when a signal indicates the prospect is using or evaluating Amplitude"), only use that objection when a Lusha or user-supplied signal indicates that competitor; otherwise pick a different objection from the same persona's list; (b) if §3 excludes a competitor entirely (e.g., "Mixpanel is not a positioning target — do not name it in any draft"), skip any objection that names that competitor and pick a different one from the same persona. **Do not invent customer names, case studies, or specifics not in the brief/signals** (Hard Rule). When a proof anchor would strengthen the email but no real one is available, use a placeholder per the no-hallucination rule (e.g., `[PLACEHOLDER: customer reference]`). Email 2 is NOT a re-stated value prop.
- **Email 3 (break-up):** explicitly close the loop — acknowledge non-response, offer to resume later, no re-pitch. NOT a fourth outreach attempt. Soft door-open patterns: *"happy to pick this back up whenever the moment is better"*, *"feel free to reach out whenever the need comes up"*.
- **Thread coherence:** signal references in E2/E3 must align with E1 — do not introduce a new primary anchor that contradicts or ignores what E1 used.
6. **Regenerate** — re-draft a specific contact or the whole batch.
7. **Save output to file** — when the host has filesystem write access, offer `lusha-outreach-sequence-<YYYY-MM-DD>.csv`. Never write silently — ask and confirm the path. Otherwise emit CSV inline as a code block.
8. **Fill placeholders** *(offered only when drafts contain `[PLACEHOLDER: ...]` strings)* — list each placeholder with contact + email number + what's needed, ask the user to provide values one batch at a time, then re-render only the affected emails. Loop until all placeholders are filled or the user signals they want to stop.
9. **Add personalization (Lusha enrich)** *(offered after E1 is drafted, for contacts that have no Lusha-enrich personal touch yet)* — add a confirmed-career-history opener to Email 1 per the **8c.0 Personalization Gate**. If Lusha enrich data for these contacts is already in the conversation, apply the gate directly — no new call and no additional quota use. Otherwise **load and use the `enrich-contact` skill** to fetch it — selecting this item is the user's authorization to enrich, and `enrich-contact` owns the existing-account quota handling. Do not impose a separate estimate-and-wait gate before enriching. For each contact, apply the gate — add a personal touch only on a confirmed prior employer with a specific, non-obvious insight; otherwise leave the standard opener untouched (padding is worse than nothing). This menu item handles *personalization* (Lusha-sourced) only; it does not touch custom signals. Re-render only `email_1_*` columns and report, per contact, personal touch added (with the insight) or skipped (with the reason).
The menu is conversational — apply overrides in place, re-render only affected columns, and keep going until the user signals satisfaction.
## Hard Rules
- **The brief is required.** No brief in conversation → refuse and point to `outreach-research`. No generic template, no inline intake.
- **Draft only; never send.** Do not send or publish emails or LinkedIn messages, submit external forms, update campaigns, execute external actions, or claim that any such action occurred.
- **Never read the host filesystem unprompted.** Brief and audience detection scan conversation only.
- **Never invoke Lusha MCP tools directly from this skill.** All signal work delegates to **`signal-prospect`** (load `SKILL.md` + `references/signal-guide.md` first). All enrichment delegates to **`enrich-contact`** (load `SKILL.md` first). Do not run signal-prospect Steps 4–8 (prospecting/enrich for new leads) from here.
- **`reason_for_invocation` must start with `outreach-sequence: `.** Every Lusha MCP tool call during this workflow — filter discovery, signal harvest, contact enrich, entitlement checks, or any other MCP tool — must prefix `reason_for_invocation` with `outreach-sequence: ` plus a brief call-specific reason. Applies even when the call follows delegated **`signal-prospect`** or **`enrich-contact`** guidance.
- **Always load `signal-prospect` first, then follow the Signal Discovery — Procedure** for filter discovery, intent mapping, sub-filter discipline, and per-contact harvest on the known audience. Never invent a signal ID.
- **Both company-side and contact-side signal search run by default.** When harvest is active (user did not choose "none" in Step 5), run BOTH through `signal-prospect` for every contact. Running only one collapses half of the available signal surface. The only acceptable single-endpoint run is when the user's Step 5 selection scoped exclusively to families that live on one endpoint (e.g., "funding rounds only" → company-side only). "All" or unspecified Step 5 selection always means both.
- **Never invent competitor names in drafts from prior knowledge.** When §3 lists competitors, you may name those competitors in drafts when a signal indicates relevance. When §3 is empty, do not invent or propose competitor names from prior knowledge — and if a signal (user-supplied `intent_signal`, harvested Lusha signal, or batch signal) surfaces a competitor name not in §3, do not silently use it. Surface the conflict to the user once at Step 8b.5 and apply their choice to the batch.
- **Never invent customer names, case studies, specific metrics, or proof points not present in the brief or harvested signals.** This rule applies to all drafted copy — subject lines, body text, and especially Email 2 proof-point anchors. Hallucinations are unacceptable even when they would improve the draft. If a draft would benefit from a concrete proof anchor (e.g., a customer reference, a use-case metric, a named outcome) and no real one is available from the brief or signals, **use a placeholder in the format `[PLACEHOLDER: <what's needed>]`** (e.g., `[PLACEHOLDER: customer reference]`, `[PLACEHOLDER: outcome metric]`, `[PLACEHOLDER: case study]`). Placeholders make the gap visible to the user; fabrication hides it.
- **Email-1 personal touches come only from confirmed Lusha career history; padding is worse than nothing.** A personal touch in Email 1 may be drafted only from Lusha enrich data showing a confirmed prior employer (non-empty `previous_employment`, or a verified secondary email-domain that resolves to a known company) AND only when a specific, non-obvious, relevant insight can be drawn from it. Never personalize from weak proxies — city / region / "scene", company national origin, URL-handle or username trivia, phone area-code or country-code inference, or restating funding / headcount / news the recipient already knows — and never fabricate career history when the enrich data has no confirmed prior employer. When the gate does not pass, add no personal touch and use the standard persona-pain / signal-anchored opener. Personalization is Lusha-sourced only: a user-supplied custom signal (batch signal, CSV column, or uploaded file) is not a personalization source even if it contains career history. A personal touch may assert only the confirmed employer, the contact's confirmed title/role as enrich returns it, and that employer's widely-known business; never assert what the contact personally did or was responsible for there beyond the confirmed title — inventing a role-specific responsibility from a real employer is still padding. Scope: Email 1 only.
- **Never invent a persona not in the brief.**
- **Honour every brief `MISSING:` or `unconfirmed` marker.** Soften or omit dependent claims at draft time.
- **State existing-account quota use before large harvests.** Estimate and confirm before signal harvest when the audience has more than 10 contacts (via `signal-prospect` conventions). Never suggest changing the user's account entitlement.
- **Never run a web search silently.**
- **Steps 5, 6, and 7 are REQUIRED before drafting.** After the brief and audience are loaded, you MUST ask Step 5 (signal preferences), Step 6 (common batch signal), and Step 7 (sample gate) in your first response — unless Step 5 was pre-answered by explicit skip authorization. Do not proceed to Step 8 until all three have been asked. Silence is not skip authorization.
- **Step 5 is the contract — bi-directional.** Never harvest a signal type the user did not select. Never skip harvest entirely unless the user explicitly authorized skip (e.g., "skip the signal harvest", "no signal harvest needed", "don't call MCP tools"). Absence of signal direction is NOT skip authorization — ASK the user in your first response.
- **Conditional output columns.** Never emit empty `email_2_*` / `linkedin_message_*` columns for undrafted messages.
- **Iterate until the user is happy.** The post-draft menu is a loop, not a one-shot.
- **No em-dashes in drafted recipient-facing copy.** Email subject lines, email bodies, LinkedIn connection requests, and LinkedIn DMs must NOT contain the em-dash character (— / U+2014). The em-dash is a recognized AI-writing signature and recipients pattern-match against it. Replace with appropriate alternatives based on the structural role:
- **After a greeting** (`Hey Sarah — when...`) → use a comma: `Hey Sarah, when...`
- **For asides / parentheticals** (`the data is in — and that's the gap`) → use a comma, period, or parentheses: `the data is in, and that's the gap` or `the data is in. That's the gap.`
- **For explanations / appositives** (`Acme Analytics — no Jira ticket required`) → use a colon, period, or hyphen: `Acme Analytics: no Jira ticket required` or `Acme Analytics. No Jira ticket required.`
- **For range or pause** → use a period or comma for pause; use a hyphen (-) for range.
- **Mandatory final scan:** before presenting any draft and before writing any file, scan every drafted subject and body in the batch — including bodies retained or carried over from an earlier turn, even for contacts you did not re-draft — for the `—` (U+2014) character, and apply the substitutions above to each occurrence. Do not state or self-certify em-dash compliance unless this scan was performed.
This rule applies only to drafted recipient-facing copy. The skill text itself, brief content, placeholder marker syntax (`[PLACEHOLDER: ... — e.g., "..."]`), and ChatGPT's own conversational responses to the user are NOT affected.
Referenced files: 1
prospect4.65 KB
--- name: prospect description: > Build a targeted list of contacts or companies from Lusha and return verified phone numbers alongside emails. Use when the user says "find me [title] at [company type]", "build a list of [ICP description]", "prospect [criteria]", "who should I be calling at [industry]", or any request to generate a lead list from an ICP or persona description. --- # Prospect Go from an ICP description to a ranked, phone-enriched lead list. Filters are resolved before search — never guess filter values. ## Step 1 — Parse the ICP Extract structured filters from the user's natural language description. Some filters take free-form text directly; others must be resolved to canonical values first. **Contact filters (`prospecting_contact_search`):** - Job titles → pass directly as `jobTitles` (free-form strings, e.g. "VP of Sales"). No resolution call needed. - Department / seniority → resolve via `prospecting_contact_filters` (type: `departments`, `seniority`). Use these for broad role targeting when a specific title isn't given. - Country → resolve via `prospecting_contact_filters` (type: `all_countries`); Location → type: `locations` (requires `locationSearchText`). **Company filters (`prospecting_company_search`):** - Industry → resolve via `prospecting_company_filters` (type: `industries_labels`) - Size → resolve via `prospecting_company_filters` (type: `sizes`) - Revenue → resolve via `prospecting_company_filters` (type: `revenues`) - Location → resolve via `prospecting_company_filters` (type: `locations`, requires `q`) - Tech stack → resolve via `prospecting_company_filters` (type: `technologies`, requires `q`) - Buying intent → resolve via `prospecting_company_filters` (type: `intent_topics`) Resolve every non-title filter to canonical values before searching — passing raw natural-language strings as structured filter values is the most common cause of search failures. Each `prospecting_*_filters` call resolves one filter type; run the independent lookups in parallel. If the ICP is too vague to resolve (no title, no industry, no company size), ask one clarifying question before proceeding. At minimum, a title or department and at least one company-level constraint are required. See `references/filter-guide.md` for filter resolution details. ## Step 2 — Search Companies Use `prospecting_company_search` with resolved company filters. Request up to 25 results. This scopes the contact search to qualified accounts. If the user only specified contact-level criteria (no company filters), skip this step and go directly to Step 3. ## Step 3 — Search Contacts Use `prospecting_contact_search` with resolved contact filters. Scope to the company results from Step 2 where applicable. Request up to 25 results. ## Step 4 — Enrich Top Results Search results are previews — they carry no phones/emails but include a `canReveal[]` list per contact showing which fields can be revealed and the existing-account quota required in `canReveal[].credits`. Use `prospecting_contact_enrich` with the contact `id`s to reveal phones and email. Pass `reveal` set from the results' `canReveal[].field` to control exactly which fields use the user's existing-account quota. Up to **50** contacts per call — split larger sets across calls. Before enriching, sum the `canReveal[].credits` for the fields you'll reveal, describe this as quota already included in the user's existing Lusha account, and wait for confirmation on large batches. This workflow cannot change the user's account entitlement. If the existing entitlement is insufficient, offer to continue with previews or a smaller batch. ## Step 5 — Present the Lead List ### Filters Applied Show the user exactly what was used so they can verify: | Filter | Value | |--------|-------| | ... | ... | ### Lead List | # | Name | Title | Company | Industry | Size | Direct Phone | Mobile | Email | Intent Signal | |---|------|-------|---------|----------|------|-------------|--------|-------|---------------| - Surface direct phone and mobile as separate columns — do not merge or hide them - Mark missing phone numbers with `—` not blank cells - Include intent signal column only if `intent_topics` filter was used ### Summary - Results found: X (showing top Y) - Contacts with verified phone: Z - Existing-account quota used: N ## Step 6 — Offer Next Actions 1. **Refine** — adjust filters and re-run 2. **Add intent filter** — narrow to companies actively researching a topic 3. **Add tech stack filter** — narrow to companies using a specific technology 4. **Run signal-prospect** — cross this list against current buying signals 5. **Export** — format as CSV for copy-paste
Referenced files: 1
signal-prospect7.93 KB
---
name: signal-prospect
description: >
Find companies or contacts triggered by a buying signal, then surface verified phone numbers
for the right decision makers. Use when the user says "find companies that just raised funding",
"who's surging in sales hiring right now", "show me companies with headcount growth",
"find contacts who just got promoted", "who changed jobs recently in [industry]",
"show me [title] at companies that just [signal event]", or any request that starts
with a real-world trigger rather than a static filter.
---
# Signal-Prospect
Start from a buying signal — company or contact level — and end with a call-ready list of enriched decision makers. This is Lusha's core differentiated workflow: trigger → identify → phone reveal.
## Step 1 — Identify the Signal Mode
Determine which mode applies based on the user's request:
**Company signal mode** — the trigger is something happening at a company (funding, hiring surge, headcount change, news event). Goal: find the right people at those companies and get their phones.
**Contact signal mode** — the trigger is something happening to a person (got promoted, changed company). Goal: find those people and get their updated contact details.
If unclear, ask: *"Are you looking for companies showing a specific signal, or individual contacts who recently changed roles or employers?"*
## Step 2 — Discover Available Signal Types and Sub-Filters
These tools are the authoritative source of valid signal identifiers — never assume a signal type exists without confirming it here, since invalid values are rejected with a 400.
**Company signals:** Call `signals_company_filters` with no `filterType` to get the directory: `{ signalTypes, availableFilters: [{ filterType, requiresQuery }] }`. To enumerate the values for a sub-filter, call it again with `filterType` set to `newsEventTypes`, `hiringByDepartments`, or `hiringByLocations` (`hiringByLocations` requires a `query`).
**Contact signals:** Call `signals_contact_filters` to get the supported contact signal types (e.g. `promotion`, `companyChange`, `allSignals`).
## Step 3 — Map User Intent to a Signal Type
Match the user's phrasing to a signal identifier returned in Step 2. The table is a starting point — always validate the identifier against the live directory before using it:
| User says | Signal type (`names`) | News sub-type (applied in Step 5) |
|-----------|----------------------|-----------------------------------|
| "raised funding / Series A/B/C / IPO" | `financialEventsNews` | `Funding Round`, `IPO`, `Strategic Investment` |
| "surging in hiring / lots of open roles" | `surgeInHiring` | — |
| "growing fast / headcount up" | `headcountIncrease3m` / `headcountIncrease6m` | — |
| "hiring sales reps" | `surgeInHiringByDepartment` (+ `filterByDepartment`) | — |
| "new partnership / new customer" | `commercialActivityNews` | `New Customer`, `Partnership`, `New Location` |
| "new product launch" | `productActivityNews` | `Product Launch`, `Product Integration` |
| "executive just joined / new CRO" | `peopleNews` | `Executive Hire`, `Executive Departure` |
| "contact just got promoted" | contact signal `promotion` | — |
| "contact changed company / new job" | contact signal `companyChange` | — |
The signal **type** (`names`) is what the discovery search in Step 4 accepts. The **news sub-type** (right column) is a different mechanism — it cannot be passed to the discovery search; it is applied in Step 5 via `signals_company_filters` values. If the user's intent doesn't map cleanly, present the closest available types and confirm.
## Step 4 — Discover by Signal (Search)
Discovery narrows the population to companies/contacts that currently have the signal. The `signals` filter on the prospecting search tools is what does this — it accepts signal **type names** plus hiring sub-filters only.
**Company signal mode** — `prospecting_company_search` with:
```
signals: {
names: ["<signal type>", ...], // OR-combined; from Step 2/3
startDate: "YYYY-MM-DD", // optional; defaults to last 6 months
filterByDepartment: [{ department }], // optional; for surgeInHiringByDepartment
filterByLocation: [{ country, state }]// optional; for surgeInHiringByLocation
}
```
Combine with ordinary company filters (industry, size, location — resolved via `prospecting_company_filters`) to scope the account population. News-event types like `Funding Round` **cannot** be narrowed here — pass the parent type (`financialEventsNews`) and narrow in Step 5.
**Contact signal mode** — `prospecting_contact_search` with `signals: { names: [...], startDate? }` (contact signals support `names` + `startDate` only). Request up to 50 results (`page_size`).
## Step 5 — Pull Signal Detail (Events, Dates, News Sub-Type)
The discovery search returns the matched companies/contacts but not the underlying signal events or their dates. To surface the actual event, its date, and to keep only a specific news sub-type, run a signals lookup on the matched Lusha IDs:
**Company mode** — `signals_companies_get` with the matched company IDs. Pass `filters.include.newsEventTypes` (e.g. `["Funding Round"]`), `hiringByDepartments`, or `hiringByLocations` to keep only the events you care about. Returns the signal events, date window, and usage metadata.
**Contact mode** — `signals_contacts_get` with the matched contact IDs → promotion / company-change events with dates.
This step uses quota already included in the user's existing Lusha account, so run it only on the shortlist the user intends to act on and state the estimated quota usage first. If you have company/contact identifiers but no Lusha IDs (e.g. a user-supplied list of domains), use `signals_companies_search` / `signals_contacts_search` instead — they resolve the identifier and return signals in one call.
## Step 6 — Find Decision Makers (Company Signal Mode Only)
For the matched companies, use `prospecting_contact_search` scoped to them via `companyDomains` or `companyNames`, plus the target role — `jobTitles` (free-form) or resolved `seniority` / `departments`. If no role was specified, ask: *"What role are you looking to reach at these companies?"*
Skip this step in contact signal mode — the triggered contacts are already the targets.
## Step 7 — Enrich and Reveal Phones
Use `prospecting_contact_enrich` with the contact `id`s to reveal direct and mobile numbers. Set `reveal` from each result's `canReveal[].field`; up to **50** contacts per call. Before enriching a large batch, state how much existing-account quota the selected fields will use. This workflow cannot change the user's account entitlement. If the existing entitlement is insufficient, offer to continue with previews or a smaller batch.
## Step 8 — Present Results
### Signal Used
State exactly which signal was applied and what it means (e.g., "Funding Round events since 2026-05-01").
### Results
**Company signal mode:**
| # | Company | Signal | Signal Date | Contact Name | Title | Direct Phone | Mobile | Email |
|---|---------|--------|-------------|-------------|-------|-------------|--------|-------|
**Contact signal mode:**
| # | Name | Signal | Previous Role | New Role / Company | Direct Phone | Mobile | Email |
|---|------|--------|--------------|-------------------|-------------|--------|-------|
- Lead with phone columns — never bury them at the end
- Mark missing phones with `—`
- Surface the signal date (from Step 5) so the user knows how fresh the trigger is
### Summary
- Companies / contacts matched: X
- Decision makers found: Y
- Verified phones revealed: Z
- Existing-account quota used: N
## Step 9 — Offer Next Actions
1. **Narrow by geography or company size** — apply additional filters to the matched companies
2. **Change the target role** — re-run Step 6 with a different title or seniority
3. **Run a different signal** — try a related signal type (e.g. also check `headcountIncrease3m` alongside funding)
4. **Export** — format as CSV
Referenced files: 1
Package details
Publisher declarations from the archived package. These are separate from our research and the live service's terms.
- Package author
- Lusha
Package observed Oct 2, 2026.
Technical details
- First seen
- Sep 30, 2026 · 22:02 UTC
- Last seen
- Oct 2, 2026 · 00:00 UTC
- Collection status
- Collected
plugin_asdk_app_69e5f69b54d8819185e1638e73c15e3b
Download plugin data (JSON)