A database and toolkit for cold-outreach internship hunting, split into two tracks:
industry/: paid-internship outreach to companies (HR / recruiter contacts) plus a scheduled career-page watcher that alerts on new entry-level postings.research/: research-internship outreach to faculty at IITs, NITs, and IIITs, organized by institute type, then state, then city.
Shared templates/ hold the email copy, scripts/ hold all automation, and state/ holds the career-watcher's saved state.
Contains personal data. The CSVs hold scraped company emails, named HR/recruiter contacts, faculty emails, and LinkedIn profiles. Use the data responsibly, keep it accurate, and follow anti-spam / consent norms when contacting anyone.
outreach-emails/
├── README.md # this file
├── requirements.txt
│
├── industry/ # TRACK 1 : company / HR outreach
│ ├── emails.csv # ~400 companies (source)
│ ├── hr_contacts.csv # ~1,300 named HR contacts (source)
│ ├── extra_contacts.csv # externally imported contacts (source)
│ ├── career_pages.csv # pages the watcher monitors
│ ├── all_emails.csv # merged reference pool
│ ├── dead_removed.csv # pruned / bounced contacts
│ ├── *_companies.csv # per-city company source sheets
│ ├── location_sheets/ # regional reference data
│ ├── batches/ # GENERATED: batch_NN.csv (100 contacts each)
│ ├── city/ # GENERATED: city_<city>_NN.csv (per-city sends)
│ └── unsorted/ # GENERATED: unsorted_NN.csv (no city detected)
│
├── research/ # TRACK 2 : faculty / research outreach
│ ├── README.md # research-track details + schema
│ ├── faculty_master.csv # single source of truth (merged + deduped)
│ ├── dead_removed.csv
│ ├── batches/ # GENERATED: research_batch_NN.csv
│ └── faculty/
│ ├── iits/ <state>/<city>/<institute>.csv
│ ├── nits/ <state>/<city>/<institute>.csv
│ └── iiits/ <state>/<city>/<institute>.csv
│
├── templates/ # email copy for both tracks
│ ├── research_cold.md
│ ├── follow_up_1.md
│ └── follow_up_2.md
│
├── scripts/ # all Python automation
│ ├── gen_batches.py # industry -> industry/batches/
│ ├── gen_city_batches.py # industry -> industry/city/ + industry/unsorted/
│ ├── gen_csv.py # rebuild emails.csv + hr_contacts.csv
│ ├── import_external.py # import external sheets -> extra_contacts.csv
│ ├── make_sheet.py # build the Excel workbook
│ ├── career_watch.py # career-page watcher
│ └── add_*.py # one-off contributor batch appenders
│
├── state/ # career-watcher state (do not hand-edit)
│ ├── career_state.json
│ ├── career_alerts.md
│ └── career_alerts.log
│
└── .github/workflows/career-watch.yml # runs scripts/career_watch.py twice a day
Why two tracks? The data shape and the pitch differ. Industry outreach targets companies by hiring need (HR contacts). Research outreach targets professors by research area. Keeping them separate means each has its own schema, templates, and batch pipeline without cross-contaminating the other.
How the scripts find their data. Every script in scripts/ resolves paths relative to its own location, not the current working directory, so you can run them from anywhere:
python scripts/gen_batches.py
python scripts/career_watch.pyThey read from industry/ and write generated output into industry/batches/, industry/city/, and industry/unsorted/. The career watcher reads industry/career_pages.csv and writes its state to state/.
- ~400 companies (Dubai/UAE, Gandhinagar/GIFT City, and pan-India)
- ~1,300 named HR contacts with direct emails
- ~130 high-priority targets
- ~25 verified direct HR/careers/recruitment inboxes
| File | Rows | Schema |
|---|---|---|
industry/emails.csv |
~400 companies | #, Company, Website, Category, General Contact Email, Email Type, HR Person Name, HR Person Title, HR Person Direct Email, LinkedIn Profile, Priority, Notes |
industry/hr_contacts.csv |
~1,300 | Company, Name, Title, Email, LinkedIn, Action |
industry/extra_contacts.csv |
~12,900 | same schema as hr_contacts.csv (externally imported) |
industry/career_pages.csv |
~1,000 | Company, URL, Location, EntryLevel, Notes |
Categories: Fintech, PropTech, HealthTech, EdTech, AI/ML, SaaS, Web Dev, App Dev, FoodTech, Logistics, Payments, Cybersecurity, Digital Marketing, IoT, Embedded, ERP, SAP.
Regions covered: Dubai/UAE, GIFT City / Gandhinagar / Infocity, and Indian metros (Bengaluru, Delhi NCR, Hyderabad, Pune, Chennai, Mumbai, Lucknow, and more via industry/location_sheets/).
external sheets ──import_external.py──┐
pasted lists ────add_*.py────────────┤──► industry/extra_contacts.csv
│
industry/emails.csv + hr_contacts.csv + extra_contacts.csv
│
┌───────────────────────┼───────────────────────┐
gen_batches.py gen_city_batches.py make_sheet.py
│ │ │
industry/batches/ industry/city/ + Excel workbook
batch_NN.csv industry/unsorted/ (4 sheets)
| Script | What it does |
|---|---|
gen_csv.py |
Rebuilds industry/emails.csv + industry/hr_contacts.csv from data embedded in make_sheet.py. |
import_external.py |
Imports external contact files into industry/extra_contacts.csv, deduping against existing rows. |
add_*.py |
One-off appenders for hand-pasted / per-city contributor lists. |
gen_batches.py |
Splits the merged pool into industry/batches/batch_NN.csv of 100 each. |
gen_city_batches.py |
Same pool split per city into industry/city/, with location-less rows in industry/unsorted/. |
make_sheet.py |
Builds the Excel workbook (All Companies, Priority, Named HR Contacts, Email Template). |
career_watch.py |
Career-page watcher (see below). |
Monitors company career pages and alerts when new postings appear, focusing on entry-level / fresher roles.
industry/career_pages.csv: pages to watch (Company, URL, Location, EntryLevel, Notes).scripts/career_watch.py: fetches each page, extracts job-like entries, diffs againststate/career_state.json, and writes new entries tostate/career_alerts.md(consumed by the Action) andstate/career_alerts.log..github/workflows/career-watch.yml: runs twice a day, opens a GitHub issue on new entries, and commits the updated state.
python scripts/career_watch.py # first run seeds state silentlyEntry-level focus: postings that look like fresher/graduate/trainee/intern/0-1 yr are flagged. Rows with EntryLevel=yes only alert on entry-level postings.
Rendering JS (SPA / MNC portals): static fetch cannot read SPA portals (Infosys joblist, TCS iBegin, etc.). The Action installs Playwright and runs with RENDER=1. Locally:
pip install playwright && playwright install chromium
RENDER=1 python scripts/career_watch.pyOptional email alerts: set repo secrets SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, ALERT_TO.
Cold-outreach to faculty at IITs, NITs, and IIITs for research internships. Full details and the CSV schema live in research/README.md.
research/faculty/
├── iits/ <state>/<city>/<institute>.csv
├── nits/ <state>/<city>/<institute>.csv
└── iiits/ <state>/<city>/<institute>.csv
Example: research/faculty/iits/maharashtra/mumbai/iit-bombay.csv
name,state,city,institute,institute_type,department,email,research_area,personal_site,priority,status,notes
institute_type:IIT|NIT|IIITstatus:queued / sent / follow-up-1 / replied / accepted / rejected / no-response
state, city, and institute_type are columns as well as folders, so faculty_master.csv stays the single source that a batch generator reads from, and the folders are just a convenient view.
pip install -r requirements.txtGenerate the sendable industry batches / spreadsheet:
python scripts/gen_batches.py # -> industry/batches/batch_001.csv, ...
python scripts/gen_city_batches.py # -> industry/city/city_bengaluru_01.csv, ...
python scripts/make_sheet.py # -> Excel workbookmailer/ is a small CSV-to-email sender for the outreach itself. It renders a
template once per recipient and sends over SMTP. Dry run is the default, so
nothing is sent unless you pass --send. Full details in mailer/GUIDE.md.
- Credentials (for real sends). Copy the env template and fill it in:
For Gmail,
cp mailer/.env.example mailer/.env
SMTP_PASSis an App Password (16 chars), not your account password..envis gitignored and must never be committed. - Put your resume at
mailer/resume.pdf(gitignored) so it gets attached, then setfrom_name,reply_to, andtemplate_varsinmailer/settings.json, and editmailer/templates/*.txtto your own words.
# preview what would go out (sends nothing):
python mailer/send.py --file industry/batches/batch_001.csv
# actually send, capped, resume attached:
python mailer/send.py --file industry/batches/batch_001.csv --limit 50 --send
# follow-up template:
python mailer/send.py --file industry/batches/batch_001.csv --template followup --sendEvery send is logged to mailer/sent_log.csv (gitignored); addresses already in
it are skipped, so re-running a batch never double-sends.
.github/workflows/mailer.yml lets you trigger a
send from the Actions tab (workflow_dispatch), so you don't need your
laptop. It is manual only and dry-run by default (no cron, to avoid
accidental sends).
Add these repository secrets (Settings -> Secrets and variables -> Actions):
| Secret | What |
|---|---|
SMTP_USER |
the sending address |
SMTP_PASS |
the Gmail App Password |
RESUME_PDF_BASE64 |
your resume, base64-encoded, so it stays out of git |
Encode the resume once:
base64 -w0 resume.pdf # paste the output into the RESUME_PDF_BASE64 secretThen Run workflow with inputs: file (e.g. industry/batches/batch_001.csv),
template, limit, and dry_run (leave true to preview, set false to
send). The dedup log is per-run on Actions (it is not committed back, to keep
recipient addresses out of the repo), so send each batch in a single run.
- Personalize every outreach email for higher response rates.
- Verify contact information before sending.
- Avoid bulk / spam sends; respect unsubscribe requests and anti-spam rules.
industry/batches/,industry/city/,industry/unsorted/, and the.xlsxare generated. Recreate them any time from the source CSVs with the commands above.industry/dead_removed.csvandresearch/dead_removed.csvtrack contacts pruned as dead/bounced.- Scripts resolve their own paths, so they work regardless of the directory you run them from.