| name | lead-enrichment-engine |
| description | Research companies and find decision-makers across LinkedIn, Google, and news sources. Builds enriched prospect dossiers with outreach hooks, prospect scores, and exports to Google Sheets. |
Lead Enrichment Engine
Given a company name, a list of target accounts, or ICP criteria, research and enrich prospects with company intelligence, decision-maker profiles, recent news, and personalized outreach hooks.
When to Use
- "Research these 10 companies and find the right person to contact"
- "Build a dossier on Acme Corp - who should I reach out to?"
- "Enrich this prospect list with company data and recent news"
- "Find 20 VP Marketing leads at Series B SaaS companies"
- "Who's the Head of Growth at [Company]?"
Required Context (ask if not provided)
- Input type: Single company, list of companies, or ICP criteria (role + industry + size + location)
- Target role(s): VP Marketing, Head of Growth, CTO, etc. (default: VP/Director level)
- Volume: How many companies or prospects (default: 10)
- Output preference: In-chat report, Google Sheet, or both
- Enrichment depth: Quick (name + title + company) or Full (dossier with news, posts, hooks)
Prerequisites
- User must be logged into LinkedIn in the browser (for profile research)
- Google Sheets access for export (optional)
Connect Services (if exporting to Sheets)
await requestApproval({
reason: "Need Sheets access to export enriched prospect list",
type: "service_auth",
payload: { provider: "google", service: "drive" }
})
Safety Rules (CRITICAL)
- Maximum 30 LinkedIn profile visits per session. Hard limit.
- Wait 8-15 seconds between profile visits (randomized).
- Maximum 5 company page visits per session.
- Maximum 3 LinkedIn search result pages per query.
- Wait 5-8 seconds between search page loads.
- Read-only: No connection requests, messages, likes, or comments.
- Stop immediately on any CAPTCHA, verification prompt, or rate-limit warning.
- Randomize all wait times: base_delay + random(0, extra_seconds).
Workflow A: Company Intelligence Gathering
Step 1: Accept Input
var companies = ["Acme Corp", "Widget Inc", "StartupXYZ"]
var sheetData = await google.sheets.getValues({
spreadsheetId: userSheetId,
range: "Sheet1!A2:A"
})
var companies = sheetData.map(row => row[0]).filter(c => c && c.trim())
console.log("Target companies: " + companies.length)
Step 2: Google Search for Company Intel
For each company, search Google for basic information:
var companyIntel = []
var page = await browser.newtab("https://www.google.com")
for (var i = 0; i < companies.length; i++) {
var company = companies[i]
await page.goto({ url: "https://www.google.com/search?q=" + encodeURIComponent(company + " company") })
await page.wait({ waitTime: 3 + Math.floor(Math.random() * 3) })
var snap = await page.snapshot()
var intel = {
name: company,
industry: "",
hq: "",
employeeCount: "",
founded: "",
description: "",
website: "",
fundingStage: "",
recentNews: []
}
companyIntel.push(intel)
console.log("Researched " + (i + 1) + "/" + companies.length + ": " + company)
}
Step 3: LinkedIn Company Page
for (var i = 0; i < companyIntel.length; i++) {
var intel = companyIntel[i]
await page.goto({
url: "https://www.linkedin.com/search/results/companies/?keywords=" + encodeURIComponent(intel.name)
})
await page.wait({ waitTime: 5 + Math.floor(Math.random() * 3) })
var snap = await page.snapshot()
await page.wait({ waitTime: 8 + Math.floor(Math.random() * 5) })
}
Step 4: Google News Check
for (var i = 0; i < companyIntel.length; i++) {
var intel = companyIntel[i]
await page.goto({
url: "https://www.google.com/search?q=" + encodeURIComponent(intel.name) + "&tbm=nws&tbs=qdr:m"
})
await page.wait({ waitTime: 3 + Math.floor(Math.random() * 2) })
var snap = await page.snapshot()
intel.recentNews = [
]
}
Workflow B: Decision Maker Discovery
Step 1: Search LinkedIn for Target Roles
var prospects = []
var targetRoles = ["VP Marketing", "Head of Growth", "CMO"]
for (var i = 0; i < companyIntel.length; i++) {
var intel = companyIntel[i]
for (var role of targetRoles) {
var searchQuery = role + " " + intel.name
await page.goto({
url: "https://www.linkedin.com/search/results/people/?keywords=" + encodeURIComponent(searchQuery) + "&origin=GLOBAL_SEARCH_HEADER"
})
await page.wait({ waitTime: 5 + Math.floor(Math.random() * 3) })
var snap = await page.snapshot()
break
}
await page.wait({ waitTime: 5 + Math.floor(Math.random() * 5) })
}
Step 2: Profile Enrichment (Top Prospects)
For the best-matched prospect at each company, visit their full profile:
for (var i = 0; i < prospects.length; i++) {
var prospect = prospects[i]
await page.goto({ url: prospect.profileUrl })
await page.wait({ waitTime: 8 + Math.floor(Math.random() * 7) })
var snap = await page.snapshot()
prospect.enriched = {
fullTitle: "",
tenure: "",
previousCompanies: [],
education: "",
skills: [],
about: "",
recentPosts: [],
isActive: false,
mutualConnections: [],
}
console.log("Enriched " + (i + 1) + "/" + prospects.length + ": " + prospect.name)
}
Step 3: Check Recent Activity (Optional)
await page.goto({ url: prospect.profileUrl + "/recent-activity/all/" })
await page.wait({ waitTime: 5 })
var activitySnap = await page.snapshot()
Workflow C: Prospect Scoring
Score each prospect (0-100) based on fit:
for (var prospect of prospects) {
var score = 0
var breakdown = []
if (exactTitleMatch(prospect.enriched.fullTitle, targetRoles)) {
score += 30; breakdown.push("Role match: +30")
} else if (partialTitleMatch(prospect.enriched.fullTitle, targetRoles)) {
score += 15; breakdown.push("Partial role match: +15")
}
if (prospect.enriched.isActive) {
score += 20; breakdown.push("Active on LinkedIn: +20")
}
if (prospect.connectionDegree === "1st") {
score += 25; breakdown.push("1st degree connection: +25")
} else if (prospect.connectionDegree === "2nd") {
score += 15; breakdown.push("2nd degree connection: +15")
} else {
score += 5; breakdown.push("3rd degree connection: +5")
}
if (prospect.companyIntel.recentNews.length > 0) {
score += 10; breakdown.push("Recent company news (outreach hook): +10")
}
if (tenureMonths(prospect.enriched.tenure) >= 12) {
score += 10; breakdown.push("Stable tenure >1yr (decision power): +10")
}
if (prospect.enriched.mutualConnections.length > 0) {
score += 5; breakdown.push("Has mutual connections: +5")
}
prospect.score = score
prospect.scoreBreakdown = breakdown
}
prospects.sort((a, b) => b.score - a.score)
Workflow D: Dossier Generation & Export
Generate Outreach Hooks
For each prospect, generate 2-3 personalized outreach hooks:
for (var prospect of prospects) {
prospect.outreachHooks = []
if (prospect.companyIntel.recentNews.length > 0) {
var news = prospect.companyIntel.recentNews[0]
prospect.outreachHooks.push({
type: "news",
hook: "Congrats on [" + news.headline + "]. [Relevant observation about what this means for their role]."
})
}
if (prospect.enriched.recentPosts.length > 0) {
var post = prospect.enriched.recentPosts[0]
prospect.outreachHooks.push({
type: "post",
hook: "Your recent post about [" + post.topic + "] resonated - especially [specific point]. [Bridge to your value prop]."
})
}
if (prospect.enriched.mutualConnections.length > 0) {
prospect.outreachHooks.push({
type: "mutual",
hook: "We're both connected to [" + prospect.enriched.mutualConnections[0] + "]. [Reason for reaching out]."
})
}
if (prospect.companyIntel.employeeCount && parseInt(prospect.companyIntel.employeeCount) > 50) {
prospect.outreachHooks.push({
type: "growth",
hook: "I noticed [Company] is growing the [department] team. [Observation about scaling challenges at this stage]."
})
}
}
Present Dossier Report
## Prospect Dossiers (5 companies researched)
### 1. Jane Doe - VP Marketing @ Acme Corp (Score: 85/100)
| Field | Details |
|-------|---------|
| Title | VP Marketing (2 yrs 3 mo) |
| Company | Acme Corp - SaaS, 250 employees, Series B, SF |
| LinkedIn | linkedin.com/in/janedoe |
| Connection | 2nd degree (3 mutual) |
| Activity | Active - posted 3 days ago about "content ops at scale" |
| Previous | Dir. Marketing @ PrevCo, Marketing Manager @ OtherCo |
**Company News:** "Acme raises $30M Series B" (TechCrunch, Apr 8)
**Best Outreach Hook:** "Congrats on the Series B! Scaling content ops is always
the fun challenge at that stage. Your recent post about it really resonated..."
**Score Breakdown:** Role +30, Active +20, 2nd degree +15, News hook +10, Stable tenure +10
Export to Google Sheet
var sheet = await google.sheets.createSpreadsheet({
title: "Enriched Prospects - " + new Date().toISOString().slice(0, 10),
sheets: ["Prospects", "Company Intel"]
})
var prospectHeaders = [[
"Score", "Name", "Title", "Company", "LinkedIn URL",
"Connection Degree", "Mutual Connections", "Active on LinkedIn",
"Recent Post Topic", "Tenure", "Best Outreach Hook", "Notes"
]]
var prospectRows = prospects.map(p => [
p.score,
p.name,
p.enriched.fullTitle,
p.companyIntel.name,
p.profileUrl,
p.connectionDegree,
p.enriched.mutualConnections.join(", "),
p.enriched.isActive ? "Yes" : "No",
p.enriched.recentPosts.length > 0 ? p.enriched.recentPosts[0].topic : "",
p.enriched.tenure,
p.outreachHooks.length > 0 ? p.outreachHooks[0].hook : "",
""
])
await google.sheets.updateValues({
spreadsheetId: sheet.id,
range: "'Prospects'!A1",
values: prospectHeaders.concat(prospectRows)
})
var companyHeaders = [[
"Company", "Industry", "Size", "HQ", "Founded",
"Funding Stage", "Website", "Recent News", "LinkedIn URL"
]]
var companyRows = companyIntel.map(c => [
c.name, c.industry, c.employeeCount, c.hq, c.founded,
c.fundingStage, c.website,
c.recentNews.map(n => n.headline).join("; "),
c.linkedinUrl || ""
])
await google.sheets.updateValues({
spreadsheetId: sheet.id,
range: "'Company Intel'!A1",
values: companyHeaders.concat(companyRows)
})
console.log("Exported: https://docs.google.com/spreadsheets/d/" + sheet.id)
De-duplication
- Track all profile URLs visited across searches
- If the same person appears in multiple company searches, keep only the highest-scored instance
- Flag prospects who appeared in multiple queries (sign of strong ICP fit)
Error Handling
- LinkedIn CAPTCHA: Stop immediately, report to user how many prospects were enriched before stopping
- Profile not found / 404: Skip, note in output, move to next
- Google rate limit: Switch to a different search engine or wait 60 seconds
- Company not found on LinkedIn: Use Google results only, note limited data
- Sheet export fails: Present dossier in-chat as fallback