A fully automated cold email pipeline I built to reach small tech companies in the Frisco/Plano TX area for a paid summer 2026 internship. The whole thing runs locally, reads from a CSV, sends through Gmail, and handles follow-ups automatically.
The pipeline has two versions. V1 targeted a broader list using generic contact emails. V2 was rebuilt from the ground up with named human contacts found through LinkedIn and company websites, a shorter and more personal email template, and the resume attached to every send.
For v1, most contacts were pattern-guessed from company domains (info@, careers@, hello@). The bounce rate on that batch was high enough to rethink the approach entirely.
For v2, every contact was researched via LLM. I looked up each company on LinkedIn, found the founder, CEO, CTO, or head of engineering, confirmed their name and role. Every address in v2 is tied to a real person, not a catch-all inbox.
The emails are short on purpose. The format is:
- Name, major, and school
- One sentence about what the company does (specific to them, not generic)
- One thing from my resume that actually overlaps with their work
- A soft ask about a summer internship
- Note that resume is attached
When an initial email is sent, a follow-up date is automatically set for 3 days later. A scheduled task picks these up and sends the follow-up without any manual work. If a company responded, bounced, or had a wrong address, the follow-up is cancelled automatically.
V1 batch (generic contacts)
- 41 emails sent
- 12 bounced, 1 wrong contact, 1 blocked, 1 location mismatch
- 26 delivered
- 28 follow-ups sent
- 0 useful responses
- Lesson: generic inboxes have high bounce rates and rarely reach a decision-maker
V2 batch (named human contacts)
- 20 emails sent to specific named people (founders, CEOs, engineers)
- 5 bounced
- 15 delivered
- 14 follow-ups sent
- 2 interviews, 2 offers received, both rescinded (better offers already in hand)
Combined
- 61 total emails sent across both batches
- 41 delivered
- 42 follow-ups sent
- 2 offers from 15 delivered v2 emails (13% offer rate)
companies.json hand-curated v1 company list with alignment keys
companies_v2.json v2 company list with named contacts and per-company notes
tracker.csv v1 contact email mapping
tracker_v2.csv v2 contact email mapping (all named people)
generate.py writes one personalized email per company to emails/
generate_v2.py v2 email generator using the shorter humanized template
build_master.py merges v1 data into master.csv
build_master_v2.py merges v2 data into master_v2.csv
send_emails.py sends initial emails and follow-ups via Gmail SMTP
master.csv v1 working file (41 companies)
master_v2.csv v2 working file (20 companies)
emails/ v1 personalized email files
emails_v2/ v2 personalized email files
Manil_Shangle_Resume.pdf attached to every v2 send
Create a .env file in the project root (already in .gitignore):
GMAIL_APP_PASSWORD=your16charapppassword
Generate one at myaccount.google.com/apppasswords. Requires 2-Step Verification to be on.
python3 generate.py # v1
python3 generate_v2.py # v2python3 build_master.py # v1
python3 build_master_v2.py # v2python3 send_emails.py --dry-run
python3 send_emails.py --master master_v2.csv --dry-runpython3 send_emails.py --attach Manil_Shangle_Resume.pdf
python3 send_emails.py --master master_v2.csv --attach Manil_Shangle_Resume.pdfFollow-ups go out automatically 3 days after each initial send via a scheduled task. To trigger manually:
python3 send_emails.py --followups
python3 send_emails.py --master master_v2.csv --followups| Flag | What it does |
|---|---|
--dry-run |
Preview without sending |
--only "Company" |
Send to one company only |
--limit N |
Stop after N emails |
--delay N |
Seconds between sends (default: 15) |
--test-to you@gmail.com |
Send one email to yourself without marking it sent |
--followups |
Send follow-ups instead of initial emails |
--attach path/to/file |
Attach a file to every email |
--master path/to/csv |
Use a different master CSV (default: master.csv) |