Skill
Claude Code skill that builds branded M&A / fundraising investor shortlists — local DBs + Outlook + Firecrawl + Dropcontact → Excel with 31-column transaction pipeline.
npx -y skills add Hectelion-SA/claude-investor-shortlist --skill skillAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Build a structured Excel investor shortlist (M&A or fundraising mandate) by combining local investor databases, Outlook contacts, web search (Firecrawl) and email enrichment (Dropcontact). Output is a 2-tab .xlsx (branded cover page + 31-column investor table with full transaction pipeline). Trigger when the user says "build an investor list", "investor shortlist", "shortlist for X", "M&A target list", or invokes /investor-shortlist.
SKILL.md
14.8 KB, as published. Nobody here has run it
Skill: /investor-shortlist — Investor Shortlist Builder
Objective
Full pipeline questionnaire → multi-source research → Dropcontact + Outlook + Firecrawl enrichment → branded Excel output with transaction pipeline columns.
The output file matches a standardized M&A advisory format used for sell-side, buy-side and fundraising mandates.
Configuration is read from config.yaml in the same folder as this skill. See config.example.yaml for the schema. Important keys:
dropcontact.api_key(or envDROPCONTACT_API_KEY)local_databases— list of Excel files to scanoutput.default_folder— suggested save pathbrand.*— firm name, colors, font for the cover page + table
Step 1 — Structured questionnaire (MANDATORY before any action)
Ask ALL questions below, grouped in 2-3 AskUserQuestion calls, never as bullet plain-text. If the user skips a question, re-ask with an explicit default value.
Block A — Target company & mandate
- Client company name (e.g. "Acme SA") + website (e.g. "acme.com")
- Company country:
🇨🇭 Switzerland/🇫🇷 France/🇲🇨 Monaco/🇱🇺 Luxembourg/Other - Mandate type:
Fundraise(capital growth, product development, geographic expansion)M&A sell-side(full or partial sale)M&A buy-side(external growth — target search)Refinancing / Debt(senior, mezzanine)Restructuring(capital, debt)
Block B — Target profile
-
Sector: e.g. "Real estate / Construction", "Industrial", "Tech / SaaS", "Healthcare / MedTech", "Energy", "B2B services", etc.
-
Sub-sector (optional): e.g. "Residential development", "B2B HR SaaS"
-
If Fundraise → Round:
Pre-seed(<500k)Seed(500k - 2M)Series A(2-10M)Series B(10-30M)Series C / Growth(>30M)Late stage / Pre-IPO
-
If M&A → Valuation range: e.g. "2-10M", "10-50M", "50-200M", "200M+"
Block C — Volume & typology
- Number of target investors in the shortlist:
15-20/20-30/30-50/50+ - Typology (multi-select):
Private / HNWI / Family officesFinancial institutional(PE funds, VC, AM, pension funds, banks)Strategic / Industrial(sector players, competitors, suppliers/customers)Public / Para-public(cantonal banks, sovereign funds, foundations)
- Investor geography: same country as target / pan-European / global
Block D — Sources & execution
- Source mix:
Local only(local Excel databases + Outlook contacts only)Internet only(Firecrawl/Brave search + Dropcontact)Hybrid(recommended — local + internet in parallel)
- Save path: ask for absolute Windows path, or propose default from
config.yaml > output.default_folder - Filename: auto-generated from
config.yaml > output.filename_templateor ask for custom
Step 2 — Multi-source research
2.1 — Local databases (if Local or Hybrid)
Read every file listed in config.yaml > local_databases. The skill auto-detects the header row by looking for columns matching patterns like:
company,name,firm,investor→ investor nameemail,mail,contact email→ contact emailcountry,pays→ countryfocus,sector,theme,specialty→ investment focusphone,tel,mobile→ phonefirst name,prénom,last name,nom,surname→ person
Filter by sub-sector / country / typology matching the brief. Score relevance 1-5.
If no local databases configured → skip this step and rely on internet + Dropcontact.
2.2 — Outlook contacts (Windows only, if outlook.scan_contacts: true)
Scan local Outlook contacts via PowerShell COM to identify investors already in your address book. Cross-reference by:
CompanyName(fuzzy match with identified investors)- Email domain (match with investor websites)
Reference PowerShell script (inline):
$outlook=New-Object -ComObject Outlook.Application
$ns=$outlook.GetNamespace("MAPI")
$contactsFolder=$ns.GetDefaultFolder(10)
$results=New-Object System.Collections.ArrayList
function ProcessFolder($folder, $list){
foreach($item in $folder.Items){
if($item.Class -eq 40){
[void]$list.Add([PSCustomObject]@{
FullName=$item.FullName; FirstName=$item.FirstName; LastName=$item.LastName
CompanyName=$item.CompanyName; JobTitle=$item.JobTitle
Email1=$item.Email1Address; Email2=$item.Email2Address
BusinessPhone=$item.BusinessTelephoneNumber; MobilePhone=$item.MobileTelephoneNumber
WebPage=$item.WebPage; Categories=$item.Categories
})
}
}
foreach($sf in $folder.Folders){ ProcessFolder $sf $list }
}
ProcessFolder $contactsFolder $results
foreach($store in $ns.Stores){
try{
$root=$store.GetRootFolder()
foreach($f in $root.Folders){
if($f.DefaultItemType -eq 2){ ProcessFolder $f $results }
}
}catch{}
}
$results | ConvertTo-Json -Depth 3 | Out-File -FilePath "$env:TEMP\_outlook_contacts.json" -Encoding utf8
Read the JSON in utf-8-sig (Windows BOM).
A hit in Outlook = the user already has a direct relationship with the contact → score boost, no Dropcontact call needed (Outlook email is more reliable than enriched email).
2.3 — Internet search (if Internet or Hybrid)
For each target with no clear decision-maker, use Firecrawl MCP (or your preferred web search) with focused queries:
"COMPANY_NAME" CEO managing director 2025 OR 2026
"COMPANY_NAME" managing partner OR président
"COMPANY_NAME" investor relations contact
Always double-check names (current CEO vs former, role changes, company rebrandings). Common pitfalls:
- A CEO can have left up to 12 months ago — cross-check via news
- Funds rebrand (e.g. acquisition-driven name changes) — verify current legal name
- Asset managers often have multiple legal entities — pick the one matching the investment thesis
2.4 — Dropcontact enrichment (ALWAYS, unless Outlook email already validated)
API key from config.yaml > dropcontact.api_key or env DROPCONTACT_API_KEY. Never hardcode the key in the script.
Endpoint: POST https://api.dropcontact.io/batch
Payload:
{
"data": [
{"first_name": "First", "last_name": "Last", "company": "Company", "website": "domain.com"}
],
"siren": false
}
Poll GET /batch/{request_id} every 15s (up to 40 attempts).
Returned fields: email[0].email, email[0].qualification (nominative@pro, catch-all@pro, not_found), phone[0].number.
Multi-pass strategy: if Dropcontact returns "Not found", search alternative decision-makers (CFO, Head of M&A, Managing Partner) via Firecrawl, then re-run Dropcontact.
Step 3 — Excel file generation
3.1 — File structure (2 tabs — NO Synthesis tab)
Tab 1: "Cover page" — minimal format (REQUIRED)
No content beyond the 6 lines below. Do NOT add enrichment sources, statistics, score legend, or important notes (this content goes in the chat summary post-generation, not in the file).
| Cell | Content | Style |
|---|---|---|
| B2 | {brand.firm_name} (from config) | font_family 14, primary_color, italic |
| B5 | {Client Company} — {Mandate type} | font_family 25, primary_color |
| B6 | Investor shortlist {sector context} | font_family 18, secondary_color, italic |
| B8 | Prepared for: {Title First Last [and Co-founder]}, {Company} | font_family 11 |
| B9 | Date: {DD MMM YYYY in English, e.g. "21 May 2026"} | font_family 11 |
| B10 | Targets: {N} investors {Country} ({typologies comma-separated}) | font_family 11 |
Concrete example (placeholder):
- B2: "Acme M&A LLP"
- B5: "Example SA — Series A+ Fundraise"
- B6: "Investor shortlist MedTech / FemTech Switzerland"
- B8: "Prepared for: Dr. John Doe and Jane Smith, Example SA"
- B9: "Date: 21 May 2026"
- B10: "Targets: 48 investors Switzerland (financial, family offices, strategic, public)"
Tab 2: "Investors" — 31 columns (header row = 4)
Identification block (cols 1-14):
#(rank)Category(e.g. "Developer / Promoter CH", "Asset Manager RE CH", "Pension fund CH", etc.)Investor(legal name)Country(with flag emoji)City / RegionType(e.g. "Real estate developer", "Listed RE fund manager")Focus / Specialty(sector detail + ticket size)Civ.(Mr / Ms)First nameLast nameTitle / RoleEmail(hyperlink mailto:)PhoneWebsite(hyperlink https://)
Scoring block (col 15):
15. Score (1-5 with color coding from brand.accent_*: 5=green, 4=pale blue, 3=grey, 2=orange)
Transaction pipeline block (cols 16-31):
16. Type (empty at creation — fill manually: "Hot lead", "Warm lead", etc.)
17. Status (empty — "Not contacted", "Contacted", "Replied", "Meeting", "Pitch", "DD", "Closed-Won", "Closed-Lost")
18. Contacted by (initials — from defaults.prepared_by_initials)
19. Last contact (date of last exchange)
20. First intent email sent (X = sent)
21. Email date
22. Teaser sent (X)
23. NDA sent (X)
24. Teaser & NDA date
25. IM and process letter sent (X)
26. IM & process letter date
27. Management presentation (pitch) date
28. MBO/LOI reception date
29. Dataroom opening
30. Signing
31. Closing
All pipeline columns (16-31) are empty at creation — filled in as the transaction progresses.
IMPORTANT: do NOT create a "Synthesis" tab. The file contains only 2 tabs: "Cover page" + "Investors". The statistical summary (count by category, email quality, etc.) is delivered in the post-generation chat message, not in the file.
3.2 — Branding (from config.yaml)
Read brand.* from config.yaml. Defaults shown below match Hectelion SA original styling; override in config for your firm.
# Loaded from config['brand']:
primary_color = "182E4E" # main navy
secondary_color = "0E2841" # darker navy
accent_pale = "DCEAF7" # score 4
accent_green = "6FCF9A" # score 5
accent_orange = "FFC000" # score 2
accent_grey = "F2F2F2" # score 3
alert_red = "C00000" # alerts
border_grey = "D9D9D9" # cell borders
font_family = "Cardo" # cover page + table
# Grid: Cover page 25 / 18 / 14 / 11
# Table: header 11 bold white, data 10 (dense cells)
# Same font across the whole file — single source of truth.
3.3 — Header row style (Investors table)
- Fill:
primary_colorsolid - Text: White,
font_family11pt Bold - Alignment: left, center vertical, wrap_text
- Border: thin
border_grey - Header row height: 32
3.4 — Data rows (Investors table)
- Font:
font_family10pt Investor(col 3):font_family10pt Boldprimary_colorEmail(col 12): hyperlink mailto,font_family10pt color#0563C1underlineWebsite(col 14): hyperlink https,font_family10pt color#0563C1underlineScore(col 15): centered,font_family10ptprimary_colorbold, fill by value- Data row height: 70 (wrap text)
- Freeze panes: header row + first 3 columns
Step 4 — Save
- Verify destination folder exists; create it if not
- If file already open in Excel: close Excel via PowerShell
Get-Process EXCEL | ForEach-Object { $_.CloseMainWindow() }before saving - Save the
.xlsxfile - Optionally open the file automatically:
Start-Process "{path}" - Confirm to the user with a clickable markdown link:
[{filename}.xlsx]({relative_or_absolute_path})
Step 5 — Final report to the user
After generation, present:
## Enrichment summary
**N/Total operational emails (X%)**
| Source | Count |
|---|---|
| ✅ Dropcontact nominative | ... |
| 🟦 Dropcontact catch-all | ... |
| 📇 Outlook (your base) | ... |
| ⚠ Standard to confirm | ... |
| ❓ To clarify | ... |
## Top X high-priority targets (score 5)
1. ...
2. ...
List any emails not found + reason (opaque public funds, low-visibility companies, etc.) with a recommended action (LinkedIn DM, formal letter, etc.).
Best practices
Deduplication
- Before export, dedup by email (one email = one row max)
- If same company but 2 decision-makers: keep the most senior / sector-relevant
- NEVER contact 2 people at the same company with separate emails
Contact details
- If Dropcontact email =
catch-all@pro→ flag in column, keep but mark - If
not found→ fallbackinfo@{domain}+ alternative decision-maker search (CFO, MD) - Phone: prefer direct mobile if Dropcontact returns one
Name verification
- Always validate that the decision-maker currently holds the position (CEOs change)
- Cross-reference Dropcontact name with Firecrawl news if any doubt
- For large institutions: scan the latest annual report / press release if available
LinkedIn fallback
- For non-Outlook contacts, optionally add a "LinkedIn" column with a pre-generated search URL:
https://www.linkedin.com/search/results/people/?keywords={urllib.parse.quote(f"{first} {last} {company}")} - Cell display: "🔗 Search" hyperlink colored
#0A66C2
Data security
- NEVER commit the Dropcontact API key to a public repo (config.yaml is in .gitignore)
- Excel files containing emails and phones should be treated as confidential client data
- Save shortlists to a controlled folder (OneDrive/SharePoint with client access controls)
Reference script
See build_shortlist.py in this skill folder — Python reference implementation, configurable via config.yaml.
Anti-patterns to avoid
- ❌ Skipping the questionnaire and inventing a generic list
- ❌ Calling Dropcontact without checking Outlook first (existing contacts are highest quality)
- ❌ Defaulting all emails to
info@out of laziness — always try Dropcontact first - ❌ Hardcoding the Dropcontact API key in the script — use config.yaml or env var
- ❌ Hardcoding firm branding in the script — read from
config.brand - ❌ Creating a "Synthesis" tab — the file has EXACTLY 2 tabs (Cover + Investors); the stats summary goes in the chat
- ❌ Overloading the cover page (sources, score legend, statistics) — minimal format, 6 lines only (B2/B5/B6/B8/B9/B10)
- ❌ Keeping rows with no email — by default, filter all rows without operational email before export (unless user explicitly asks to keep them)
- ❌ Forgetting the pipeline columns (16-31) — that's the main value-add of the file
- ❌ Listing CEO names without verifying they're still in post
- ❌ Skipping the cover page — it's the visual recap of the file