skills/FindEmail/SKILL.md
Enrich Excel prospect lists with contact emails via Apollo.io and Hunter.io APIs. Also supports single-domain lookup without Excel.
npx skillsauth add asets-gobizit/claude-skills find-emailInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
A specific filename is REQUIRED. Bare filenames are resolved against the fixed input folder C:\Users\dansk\Claude\FindEmail\ — no full path needed.
python find_email.py "myfile.xlsx" # Resolved against C:\Users\dansk\Claude\FindEmail\myfile.xlsx
python find_email.py "C:\full\path\file.xlsx" # Absolute path still works (override)
python find_email.py "myfile.xlsx" --no-title-filter # Generic mode — return any contacts
python find_email.py "myfile.xlsx" --dry-run # Show stats only, no API calls
python find_email.py "myfile.xlsx" --max-contacts 3 # Override contacts per company (default: 2)
python find_email.py "myfile.xlsx" --yes # Skip confirmation prompt
python find_email.py "myfile.xlsx" --no-ddg # Skip DuckDuckGo, use Apollo for domain lookup
python find_email.py "myfile.xlsx" -y --max-contacts 1 # Combine flags
# Single domain lookup (no Excel file needed — file argument omitted):
python find_email.py --domain acme.com # Find ETS contacts at acme.com
python find_email.py --domain acme.com --company "Acme Ltd" # Add company name for better results
python find_email.py --domain acme.com --no-title-filter # Any contacts, not just ETS roles
python find_email.py --domain acme.com --max-contacts 3 # Get up to 3 contacts
If you run python find_email.py with NO file and NO --domain, the script lists the available files in the input folder and exits with an error — by design, no auto-pick.
| Flag | Description |
|------|-------------|
| --no-title-filter | Disable ETS title filtering — returns any contacts |
| --max-contacts N | Override max contacts per company (default: 2) |
| --dry-run | Scan file, show stats, exit without API calls |
| --yes / -y | Skip the "Continue?" confirmation prompt |
| --no-ddg | Skip DuckDuckGo domain lookup — falls back to Apollo org enrich |
By default the skill filters Apollo results to ETS-relevant job titles only:
Use --no-title-filter for generic prospect lists not related to EU ETS/carbon.
A "Comments" column is added to the output. It flags issues per contact:
Before processing, the script calculates estimated API calls (companies_to_process × 2) and asks Continue? [Y/n]. Use --yes to skip the prompt for automation.
--dry-run scans the file and reports: total companies, already enriched (skip count), companies to process, estimated API calls. Exits without calling any APIs or modifying files.
The pipeline minimizes paid API calls by resolving domains via free methods first, then using Hunter (cheaper) before Apollo (expensive):
Domain resolution (stops at first success):
guess_domain() heuristic (free)Email discovery (stops at first success):
Expected savings: ~60-70% fewer Apollo API calls vs v1.3.
duckduckgo-search libraryAPOLLO_API_KEY, HUNTER_API_KEYpip install duckduckgo-search{name}-backup{YYYYMMDD}.xlsxC:\Users\dansk\Claude\FindEmail\
Drop your .xlsx here. The skill auto-detects the newest non-backup file.
{original}-backup{YYYYMMDD}.xlsx (same folder){original}-enriched.xlsx (same folder)Blocked email domains: Emails from data provider domains (e.g. @zoominfo.com) are automatically filtered out — these are employees of the data provider, not your target contacts. Add more domains to BLOCKED_EMAIL_DOMAINS in the code as needed.
Domain mismatch handling: Emails from a different domain than the target company are kept but flagged with a comment. The email may still be correct (subsidiaries, holding groups, rebranded companies).
No tool gives 100% accuracy — best practice: Apollo/Hunter for leads → LinkedIn to confirm → email verifier (NeverBounce, ZeroBounce) to validate.
If Email1 column already exists, rows with a filled Email1 are skipped to save API credits.
Every run produces a timestamped subfolder under C:\Users\dansk\Claude\FindEmail\FindEmail-Reports\. The bundle is the source of truth for diagnosing the skill's miss rate over time and lets a future implementer (or a re-imported run six months later) replay the exact pipeline state without re-spending API credits.
FindEmail-Reports/
└── 2026-04-27_05-05-38/
├── report.txt — human summary + new failure-class breakdown
├── run-config.json — every CLI flag, skill version, input file sha256
├── input-snapshot.xlsx — bytes of the input at run-start (replay-safe)
├── output.xlsx — copy of the enriched output at run-end
└── trace.jsonl — one JSON record per company with full pipeline trace
Each resolved domain carries a confidence score based on which resolver produced it:
spreadsheet → 1.00 (Danny supplied — ground truth)apollo_org → 0.90 (Apollo matched the company)ddg → 0.70 (DuckDuckGo top organic)guess → 0.05 (guess_domain() pattern substitution)Rows in output.xlsx are colored based on domain confidence — applied during format_output() after sorting/dedup so the color tracks the data:
The Comments column is only populated when there's something to flag (domain mismatch, low/medium confidence, Apollo lock, generic email). High-confidence wins keep the Comments cell empty — no editorial markup on rows that worked.
Replaces the lossy single "not_found" bucket. report.txt now prints a failure-class breakdown using these canonical reasons:
| Reason | Meaning |
|---|---|
| domain_resolution_failed | no resolver returned a domain |
| domain_low_confidence | only guess() resolved — too risky to trust |
| domain_mismatch_filtered | provider returned email with mismatched domain |
| generic_email_only | only role-based emails available |
| title_filter_dropped_all | Apollo had candidates but ETS title filter rejected all |
| all_providers_zero | all providers returned 0 candidates |
| lusha_rate_limited | Lusha quota / rate limit hit |
| shell_company | input matched shell-company / SPV heuristic (reserved for v2.1) |
trace.jsonl — one record per company. Key fields:
{
"run_id": "2026-04-27_05-05-38",
"company_idx": 4,
"company_input": "Abbott Laboratories",
"row_indices": [5, 6, 7],
"domain_attempts": [
{"resolver": "ddg", "result": "abbott.com"}
],
"domain_resolved": "abbott.com",
"domain_resolved_via": "ddg",
"domain_confidence": 0.70,
"email_attempts": [
{"provider": "hunter", "results_count": 3}
],
"winning_provider": "Hunter",
"contacts_count": 3,
"contacts": [{"first_name": "...", "last_name": "...", "email": "...", "title": "..."}],
"status": "found_personal",
"rejection_reason": null,
"duration_ms": 740,
"timestamp": "2026-04-27T05:06:18.123Z",
"skill_version": "2.0"
}
Designed so a downstream filter (e.g. before SuperAGI import) can reject rows where domain_confidence < 0.30 AND status == "found_personal" — the false-positive class identified in research brief 2026-04-27-research-findemail-forensic-logging.md.
python find_email.py --help — all flags visiblepython find_email.py --dry-run — shows stats, no API calls--max-contacts 1 limits to 1 per company--yes)data-ai
Automated backup skill for PKA + Obsidian + Claude memory. Snapshots pka.db (via SQLite .backup so WAL is handled safely), the Obsidian vault, Claude memory files, agent profiles, and the help-content Excel into a single timestamped zip in Zoho WorkDrive. Daily/weekly/monthly retention rotation built in. USE WHEN Danny says "backup", "run backup", "snapshot pka", or to recover from a snapshot.
testing
Run any question, idea, or decision through a council of 5 AI advisors who independently analyze it, peer-review each other anonymously, and synthesize a final verdict. Based on Karpathy's LLM Council methodology, packaged as the stress-test skill. MANDATORY TRIGGERS: 'stress-test this', 'stress test this', 'pressure-test this', 'pressure test this', 'war room this', 'council this', 'run the council', 'debate this'. STRONG TRIGGERS (use when combined with a real decision or tradeoff): 'should I X or Y', 'which option', 'what would you do', 'is this the right move', 'validate this', 'get multiple perspectives', 'I can't decide', 'I'm torn between'. Do NOT trigger on simple yes/no questions, factual lookups, or casual 'should I' without a meaningful tradeoff (e.g. 'should I use markdown' is not a stress-test question). DO trigger when the user presents a genuine decision with stakes, multiple options, and context that suggests they want it pressure-tested from multiple angles.
content-media
Weekly scan of Obsidian vault for empty/low-content notes. Moves them to zToBeDeleted/ for user review. Use when the user says "tidy obsidian", "clean obsidian", "obsidian cleanup", or on scheduled weekly run.
development
Design-first website builder. Accepts a Claude Design export (HTML or screenshot) as a visual blueprint, collects business inputs, calls Claude API to generate a design-matched index.html, and publishes to GitHub Pages. Falls back to vibe-based generation if no design is provided. Use when the user says "make-website-design", "design website", "build from design", or provides a Claude Design export for website generation.