Hailstorm helps manage internship outreach emails in a structured, trackable way.
This project was born from a practical need: sending many internship emails without losing track of who was contacted, who is scheduled next, and what still needs action. Instead of handling outreach in scattered notes, Hailstorm treats email follow-ups like todo tasks with clear states.
- Batch and personalize internship outreach emails.
- Schedule emails in each contact's local timezone.
- Track progress like tasks: inactive, scheduled, sent.
- Keep templates and CV attachments in one repeatable workflow.
pip install -r requirements.txt-
Edit
config.yaml— set your name, email, and CV path -
Create
.envfrom the template:cp .env.example .env
-
Fill in SMTP credentials in
.env:- For Gmail: create an App Password
- Set
SMTP_EMAILandSMTP_PASSWORD
-
Place your CV in
assets/cv.pdf
Edit contacts.csv with your target contacts. Required columns:
| Column | Description |
|---|---|
email |
Recipient email |
professor_name |
Professor's name |
student_name |
PhD student name (optional) |
lab_name |
Lab name |
university |
University |
timezone |
IANA timezone (e.g. America/New_York) |
research_area |
Lab's research focus |
experience_alignment |
Why your background fits |
application_number |
Program application ID (optional) |
template_id |
Template to use (filename without .txt) |
Templates live in templates/ as .txt files using Jinja2 syntax. See existing templates for examples.
Contacts in contacts.csv are inactive by default. You must explicitly schedule them before they get sent.
python3 main.py preview # preview all contacts
python3 main.py preview --to alice@mit.edu # preview specific contactpython3 main.py schedule --to alice@mit.edu --date 2026-02-24 # 8 AM in their timezone
python3 main.py schedule --to alice@mit.edu --at "2026-02-24 10:30" # specific time
python3 main.py schedule --date 2026-02-24 # schedule all inactive contactsIf no --date or --at is given, defaults to tomorrow 8 AM in the contact's timezone.
python3 main.py send # send scheduled emails whose time has arrived
python3 main.py send --dry-run # show what would sendpython3 main.py send-now --to alice@mit.edu # send specific contact now
python3 main.py send-now # send all unsent contacts nowpython3 main.py cancel --email alice@mit.edu # remove from schedule (back to inactive)
python3 main.py cancel --all # clear all schedules + send historypython3 main.py status # shows inactive / scheduled / sent per contactThink of each contact as a todo item moving through states:
inactive: Contact exists, not yet scheduled.scheduled: Contact has a planned send date/time.sent: Email was delivered and logged.
This makes internship outreach auditable and less stressful, especially when managing dozens of applications.
Set up a cron job to automatically send scheduled emails:
crontab -eAdd this line (adjust the path):
*/15 * * * * cd /path/to/Hailstorm && python3 main.py send >> cron.log 2>&1
The script checks each contact's local timezone and only sends when it's 8–9 AM in their timezone.
This project is licensed under the MIT License. See LICENSE.
Hailstorm/
├── main.py # CLI entry point
├── config.yaml # Configuration
├── .env # SMTP credentials (gitignored)
├── contacts.csv # Recipient database
├── send_log.json # Send tracking (gitignored)
├── assets/
│ └── cv.pdf # Your CV
├── templates/
│ ├── professor_v1.txt
│ └── phd_student_v1.txt
└── src/
├── cli.py # CLI commands
├── config.py # Config loader
├── contacts.py # Contact reader
├── mailer.py # SMTP sender
├── scheduler.py # Timezone scheduling
├── templater.py # Template renderer
└── tracker.py # Send log