End-to-end network outreach builder (Telegram + Gmail → curated workbook)
A year of DMs hides 70 worth-emailing contacts. Stop spending three days finding them by hand.
What
A Claude Code skill that turns the user's own Telegram DMs (and optionally Gmail) into a curated outreach list for a specific announcement (fund launch, program intake, product release). It runs a two-layer pipeline — a deterministic conversation-quality filter, then a Claude-driven relevance curation against an audience definition — and emits a styled .xlsx with three sheets (Outreach / Excluded / Legend) plus an optional auto-write into a shared team Google Sheet with multi-rule dedup against existing rows. One prompt in, ~70 well-targeted contacts and a team-sheet update out.
Why it matters
A test run on a partner's account at an early-stage investor moved 2,064 raw dialogs → 409 active in the last 12 months → 253 passing the deterministic gate → ~70 in the final Outreach sheet, in 30–45 minutes wall-clock. That replaces a multi-day manual triage of a year's worth of DMs and email. The most expensive failure it prevents is the embarrassing-but-common one of sending an "AI-native fund launch" note to a great pure-DeFi founder or to an unrelated personal contact — the kind of error that costs a relationship for zero return.
End-to-end
- Install the skill. Drop the skill folder into Claude Code's skill directory:
mkdir -p ~/.claude/skills
cp -r outreach-builder ~/.claude/skills/
- One-time setup. Get Telegram API ID/hash from
my.telegram.org/auth → API development tools; copy.env.exampleto~/.config/outreach-builder/.env; install Python deps; log in once to mint a Telethon session.
mkdir -p ~/.config/outreach-builder
cp ~/.claude/skills/outreach-builder/templates/env.example ~/.config/outreach-builder/.env
python3 -m pip install --user telethon openpyxl httpx
python3 ~/.claude/skills/outreach-builder/scripts/telegram_login.py
- (Optional) Portfolio blocklist. Drop a
portfolio.txt(one substring per line) in~/.config/outreach-builder/so group-chat extraction skips your own portfolio companies' communities. - Run the pipeline. In Claude Code, the single prompt is
Build the AI-native fund outreach list.The skill then runs, in order: preflight → list Telegram dialogs → filter to last 12 months → batched enrichment (bio + last 30 messages, rate-limited at 5 req/min/account with 350ms inter-request delay) → render compact view → Layer 1 deterministic filter → Layer 2 Claude curation → workbook scaffold → CSV ingest → group-chat extraction → team-sheet capability check → dedup → write or paste. - Layer 1 — deterministic filter (OR logic). A contact passes if it has ≥10 messages in the last-30 sample OR ≥2 distinct calendar days with at least one message. The OR is load-bearing: a single intense day is real signal, and a sparse multi-day exchange is real signal; one-shot cold pitches get cut. Defaults:
--min-msgs 10 --min-days 2. - Layer 2 — Claude curation against audience. Claude reads the compact qualified file and decides INCLUDE / EXCLUDE per contact. INCLUDE if any of: AI-native founder (agentic, infra, dev-tools, AI×crypto crossover); peer VC backing AI rounds; high-leverage amplifier (editor-in-chief, EF-tier researcher). EXCLUDE pure-crypto-no-AI-tie, internal, applicant, cold-sales, personal/family/medical. Hard rule: when in doubt, EXCLUDE with a reason. You can promote from Excluded in a click; you cannot un-send a message.
- Team-sheet capability check (7 states). Before offering auto-write, the skill returns one of
ready / partial / schema_mismatch / needs_share / no_credentials / no_sheet_id / lib_missing, each with the exact fix and time cost printed. No silent failure modes. - Dedup with ambiguous-surfacing. Four rules in order: TG-handle match (normalized) → exact full-name match → multi-word primary-name match (token sets equal) → single-word match only if Org tokens overlap. Anything hitting rule 4 with weak overlap is surfaced as ambiguous, not silently dropped. Single-first-name collisions are the most common false-positive class.
- Write safely. Auto-write uses
values().update()with an explicit row range computed from the Name column only — neverappend()(it jumps past orphan cells in other columns and writes to the wrong row).valueInputOption=RAW, neverUSER_ENTERED, so a display name like=HYPERLINK(...)can't be parsed as a formula. Both bugs cost real time in the first run; both are now permanent guardrails. - Hand-off. Skill prints the xlsx path + counts by tier and owner + the 5 most surprising excludes so the user can spot-check before sending.
Prompts
User-facing invocation (everything else is automatic):
Build the AI-native fund outreach list.
Layer-1 deterministic filter — the actual command, with tunable thresholds:
python3 scripts/conversation_filter.py --min-msgs 10 --min-days 2 # defaults
python3 scripts/conversation_filter.py --min-msgs 5 --min-days 1 # wider net
python3 scripts/conversation_filter.py --min-msgs 20 --min-days 3 # tighter net
Layer-2 audience definition baked into the skill (verbatim, used as Claude's curation criteria):
INCLUDE if any of:
- AI-native founder (agentic AI, AI infra, AI tooling/dev-tools, AI-native apps,
AI×crypto crossover). Test: would this person plausibly fit a 12-week elite accelerator?
- AI-investing peer VC: partner-level investor known to back AI / AI-crypto rounds.
- High-leverage amplifier: editor-in-chief or social lead at crypto/tech press;
EF-tier researcher who would actually retweet or cover the launch.
EXCLUDE if any of: pure-crypto-no-ai-tie, internal, applicant, cold-sales,
personal/family/medical/banking, unclear-ai-fit.
When in doubt, EXCLUDE and put them in the Excluded sheet with a reason.
False positives in the Outreach sheet are worse than misses — you can promote
from Excluded in a few clicks; you cannot un-send a message.
Team-sheet machine-readable capability check (the friend-onboarding moment):
python3 scripts/team_sheet.py check --json
Gotchas
- Override-by-handle, never by list position. When Claude builds per-contact override structures, key them by
@usernameoruser_id— never by list index. The moment a filter tightens mid-run, list positions shift and overrides silently rebind to the wrong rows. This is the worst class of curation bug because outputs still look plausible. (2026-05-04 lesson, baked into the skill.) - Never
append()into a shared sheet. First production run wrote to row 412 instead of row 109 because an orphan cell in column F shifted Google Sheets' notion of "last row". Always compute the last row from a known clean column (Name) and usevalues().update()with an explicit range. (2026-05-05 lesson.) USER_ENTEREDis formula-injection. Telegram display names are user-controlled; someone with a name like=HYPERLINK(http://evil,click)gets that parsed as a live formula. UsevalueInputOption=RAW.- Pure-crypto founders you know well are still EXCLUDE for an AI-native list. The most-counterintuitive rule; restate it explicitly so curation doesn't drift halfway through a batch.
- Read the messages, not just the bio. A "Co-founder at X" bio is irrelevant if the last 30 messages are all about the user's medical issues.
- Layer-2 batching with check-ins is mandatory, not polite. For >100 qualified contacts, batch by ~40 and summarize counts + surprising includes/excludes between batches. Silent dumps lose user trust and let bad calls accumulate.
- Telegram group scraping is rate-limited and ToS-sensitive. Use one account per workspace, throttle ≤ 30 requests/min, never scrape private groups, and store nothing about members who haven't opted into your surface.
Variations
- Lighter (single-machine, no team sheet). Stop after step 6 and produce only the local xlsx. Skips all GCP setup; works in 15 minutes from cold install.
- Heavier (Gmail-included). Add the Gmail OAuth fallback (or a Gmail MCP if available); useful for email-native networks where Telegram coverage is partial.
- Per-launch generic version. Replace the hardcoded "AI-native fund" audience definition with a runtime arg so the same machine can run "developer tools program outreach", "design partner round outreach", etc. — see the sibling
outreach-builderskill pattern. - Telegram-group source variant. Instead of (or in addition to) starting from Telegram saved-messages and Gmail, scrape selected public Telegram groups for lead surface. An agency built this against 7 industry-relevant Telegram groups (~11 000 members combined) and produced 600+ ICP-scored leads in 2 days. Steps: (a) scrape group rosters via the Telegram client API (rate-limit aware); (b) hydrate each member with public profile + bio + recent activity; (c) run the same ICP / CBI scoring you already use for inbox sources; (d) feed survivors into the outreach builder. Source-attribution: inspired by a previous-cohort speaker's prior workflow.
Tools
- Python 3.9+,
telethon,openpyxl,httpx— core pipeline - Telegram API ID/hash from
my.telegram.org/auth— free, 5 minutes - Claude Code with the skill installed — Layer-2 curation cost is paid in the user's session, no separate API key
- Optional:
google-api-python-client+google-auth+ service-account JSON for team-sheet auto-write - Optional: Gmail MCP or Gmail OAuth deps for the email-native pass