>_
MailForge
GPLv3
Python CLI · bulk personalized email

Send hundreds of emails.
Know exactly what each one says.

MailForge loads a CSV of contacts and a Jinja2 template with {{name}} / {{company}} placeholders, then sends each contact a uniquely personalized email — logging every send with automatic retry on failure.

--dry-run before you send. Always.
mailforge.py
# Preview first, always
$python mailforge.py --contacts leads.csv --template email.html --dry-run
✅ Previews generated in /preview (47 files) — open in browser to review
# Send when ready
$python mailforge.py --contacts leads.csv --template email.html --rate 10/min
✅ 45/47 sent | ❌ 2 failed (logged) | 📋 sent_log_2024-04-22.txt
The X factor

Never send a mail merge blind.

Most bulk-send scripts fire straight into your contacts' inboxes. MailForge makes proofing and pacing first-class citizens of the workflow, not an afterthought.

🔍

Dry-run HTML preview

Run with --dry-run and MailForge renders every single personalized email as its own HTML file in a local /preview folder — one file per contact, fully rendered with their real name and company. Open the folder in a browser and read through all 47 versions before a single message leaves your SMTP server. Typos, broken merge fields, and awkward phrasing get caught while they're still free to fix.

⏱

Built-in send rate limiter

Pass --rate 10/min and sends are throttled to a steady, human-plausible pace instead of blasting your entire list in one burst. Bursty sending is exactly what triggers spam filters and provider rate limits — pacing sends keeps your domain's reputation intact and your emails out of the junk folder.

Under the hood

Small script, real engineering.

No frameworks, no dashboard, no vendor lock-in — just a focused Python CLI built on the standard tools for the job.

smtplibJinja2 templatingpandasCSV contact listslogging moduleretry on failuredry-run HTML previewsend rate limitingargparse CLI
Run it

Four commands, start to finish.

  1. 1

    Install dependencies

    pandas and Jinja2, pinned in requirements.txt.

  2. 2

    Configure SMTP credentials

    Copy .env.example to .env and fill in your mail server.

  3. 3

    Preview every email

    --dry-run renders the full batch to /preview — nothing sends.

  4. 4

    Send, paced

    --rate 10/min throttles delivery and logs every result.

setup
$pip install -r requirements.txt
$cp .env.example .env
$python mailforge.py --contacts contacts.csv --template template.html --dry-run
✅ Previews generated in /preview
$python mailforge.py --contacts contacts.csv --template template.html --rate 10/min
✅ Sent — see sent_log_*.txt for the full run