# Investor Shortlist

> 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: `hectelion-sa/investor-shortlist` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add hectelion-sa/investor-shortlist`
- Raw SKILL.md: https://api.skillmd.com/api/skills/hectelion-sa/investor-shortlist/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: Hectelion-SA (https://skillmd.com/u/hectelion-sa)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/hectelion-sa/investor-shortlist

---


# 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 env `DROPCONTACT_API_KEY`)
- `local_databases` — list of Excel files to scan
- `output.default_folder` — suggested save path
- `brand.*` — 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

1. **Client company name** (e.g. "Acme SA") + **website** (e.g. "acme.com")
2. **Company country**: `🇨🇭 Switzerland` / `🇫🇷 France` / `🇲🇨 Monaco` / `🇱🇺 Luxembourg` / `Other`
3. **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

4. **Sector**: e.g. "Real estate / Construction", "Industrial", "Tech / SaaS", "Healthcare / MedTech", "Energy", "B2B services", etc.
5. **Sub-sector** (optional): e.g. "Residential development", "B2B HR SaaS"

6. **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`

7. **If M&A → Valuation range**: e.g. "2-10M", "10-50M", "50-200M", "200M+"

### Block C — Volume & typology

8. **Number of target investors in the shortlist**: `15-20` / `20-30` / `30-50` / `50+`
9. **Typology** (multi-select):
   - `Private / HNWI / Family offices`
   - `Financial institutional` (PE funds, VC, AM, pension funds, banks)
   - `Strategic / Industrial` (sector players, competitors, suppliers/customers)
   - `Public / Para-public` (cantonal banks, sovereign funds, foundations)
10. **Investor geography**: same country as target / pan-European / global

### Block D — Sources & execution

11. **Source mix**:
    - `Local only` (local Excel databases + Outlook contacts only)
    - `Internet only` (Firecrawl/Brave search + Dropcontact)
    - `Hybrid` (recommended — local + internet in parallel)
12. **Save path**: ask for absolute Windows path, or propose default from `config.yaml > output.default_folder`
13. **Filename**: auto-generated from `config.yaml > output.filename_template` or 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 name
- `email`, `mail`, `contact email` → contact email
- `country`, `pays` → country
- `focus`, `sector`, `theme`, `specialty` → investment focus
- `phone`, `tel`, `mobile` → phone
- `first 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):

```powershell
$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:
```json
{
  "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)**:
1. `#` (rank)
2. `Category` (e.g. "Developer / Promoter CH", "Asset Manager RE CH", "Pension fund CH", etc.)
3. `Investor` (legal name)
4. `Country` (with flag emoji)
5. `City / Region`
6. `Type` (e.g. "Real estate developer", "Listed RE fund manager")
7. `Focus / Specialty` (sector detail + ticket size)
8. `Civ.` (Mr / Ms)
9. `First name`
10. `Last name`
11. `Title / Role`
12. `Email` (hyperlink mailto:)
13. `Phone`
14. `Website` (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.

```python
# 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_color` solid
- Text: White, `font_family` 11pt Bold
- Alignment: left, center vertical, wrap_text
- Border: thin `border_grey`
- Header row height: 32

### 3.4 — Data rows (Investors table)

- Font: `font_family` 10pt
- `Investor` (col 3): `font_family` 10pt Bold `primary_color`
- `Email` (col 12): hyperlink mailto, `font_family` 10pt color `#0563C1` underline
- `Website` (col 14): hyperlink https, `font_family` 10pt color `#0563C1` underline
- `Score` (col 15): centered, `font_family` 10pt `primary_color` bold, fill by value
- Data row height: 70 (wrap text)
- Freeze panes: header row + first 3 columns

---

## Step 4 — Save

1. Verify destination folder exists; create it if not
2. If file already open in Excel: close Excel via PowerShell `Get-Process EXCEL | ForEach-Object { $_.CloseMainWindow() }` before saving
3. Save the `.xlsx` file
4. Optionally open the file automatically: `Start-Process "{path}"`
5. 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` → fallback `info@{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

