A professional CLI application for sending personalized mass emails. Built with Python using only standard libraries - no external dependencies required.
- π¨ Personalized emails with mail-merge functionality
- π Secure SMTP support (SSL/TLS)
- π PDF attachments (CV, cover letters, etc.)
- π¨ HTML emails with automatic plain-text fallback
- π¦ Rate limiting to avoid spam filters
- π CSV-based contact management
- π Comprehensive logging of all operations
- π§ͺ Dry-run mode for testing
- π¨ Colored terminal output for better UX
- β Status tracking to avoid duplicate sends
- Python 3.9 or higher
- Gmail account with App Password (or other SMTP server)
- No external dependencies - uses only Python standard library
- Clone or download this repository:
git clone https://github.com/gmodev/mailflow.git
cd mailflow- Copy the example environment file and configure it:
cp .env.example .env- Edit
.envwith your SMTP credentials:
nano .env # or use your preferred editor- Go to your Google Account settings
- Navigate to Security β 2-Step Verification
- Scroll down to "App passwords"
- Generate a new app password for "Mail"
- Copy the 16-character password to your
.envfile
# SMTP Configuration
SMTP_HOST=smtp.gmail.com
SMTP_PORT=465
SMTP_SSL=true
SMTP_USER=your_email@gmail.com
SMTP_PASS=your_16_char_app_password
# Sender Information
SENDER_NAME=Your Full Name
SENDER_EMAIL=your_email@gmail.comYour contacts_hotels.csv should have these columns:
email,hotel_name,city,contact_name,notes,status,sent_at
hr@hotel.com,Hotel Name,City,Contact Person,,- email: Recipient email (required)
- hotel_name: Name of the hotel (required)
- city: City location (required)
- contact_name: Person's name (optional - generic greeting used if empty)
- notes: Your internal notes (optional)
- status: SENT/SKIP/empty (managed by script)
- sent_at: Timestamp (managed by script)
Available placeholders for subject.txt and email_template_italiano.html:
{{contact_name}}- Recipient's name{{hotel_name}}- Hotel name{{city}}- Hotel city{{sender_name}}- Your name (from .env){{today}}- Current date (DD/MM/YYYY)
Dry run (simulate without sending):
python3 send_mail_merge.py \
--csv contacts_hotels.csv \
--subject subject.txt \
--html email_template_italiano.html \
--dry-runSend emails with PDF attachment:
python3 send_mail_merge.py \
--csv contacts_hotels.csv \
--subject subject.txt \
--html email_template_italiano.html \
--attachment cv.pdfSend and update CSV status:
python3 send_mail_merge.py \
--csv contacts_hotels.csv \
--subject subject.txt \
--html email_template_italiano.html \
--update-contacts# Simulate sending (dry-run)
make dry-run
# Send all emails
make send
# Test with only first 5 contacts
make send-test
# Run all tests
make test
# Clean log files
make clean# Send only first 10 emails
--max 10
# Start from row 5 (0-indexed)
--from-row 5
# Wait 5 seconds between emails
--sleep 5
# Use custom log file
--log my_custom_log.csv
# Use different .env file
--env .env.productionpython3 send_mail_merge.py \
--csv contacts_hotels.csv \
--subject subject.txt \
--html email_template_italiano.html \
--attachment CV_Gonzalo_Medrano.pdf \
--update-contacts \
--sleep 5 \
--max 20| Argument | Description |
|---|---|
--csv |
Path to contacts CSV file |
--subject |
Path to subject template file |
--html |
Path to HTML email template |
| Argument | Default | Description |
|---|---|---|
--attachment |
None | Path to PDF file to attach |
--env |
.env |
Path to environment file |
--sleep |
3 |
Seconds to wait between emails |
--dry-run |
False | Simulate without sending |
--max |
None | Maximum emails to send |
--from-row |
0 |
Start from row N (0-indexed) |
--update-contacts |
False | Update CSV with status |
--log |
outbox_log.csv |
Path to log file |
Run all tests:
make testOr run individually:
# Test template rendering
python3 tests/test_render.py
# Test CLI functionality
python3 tests/test_cli_dry_run.pyAll email operations are logged to outbox_log.csv with:
- Email address
- Hotel name and city
- Contact name
- Status (SENT/ERROR/DRY_RUN)
- Error message (if any)
- Timestamp
Build the Docker image:
docker build -t mailflow .Run with Docker:
docker run -v $(pwd)/data:/app/data mailflow \
python3 send_mail_merge.py \
--csv contacts_hotels.csv \
--subject subject.txt \
--html email_template_italiano.html \
--dry-run- Verify your Gmail App Password is correct
- Make sure 2-Step Verification is enabled on your Google account
- Check that
SMTP_USERandSMTP_PASSare set correctly in.env
- Check your internet connection
- Verify SMTP_HOST and SMTP_PORT are correct
- Some networks block port 465 - try port 587 with
SMTP_SSL=false
- Send fewer emails per session (use
--max) - Increase delay between emails (use
--sleep 10) - Personalize your template more
- Avoid spam trigger words
- Consider warming up your email account gradually
- Check that email addresses in CSV are valid
- Remove any extra spaces or special characters
- Make sure you use
--update-contactsflag - Check file permissions on the CSV file
- Verify CSV has correct column headers
- Always test first: Use
--dry-runto verify everything works - Start small: Use
--max 5for initial real sends - Respect rate limits: Keep
--sleepat 3-5 seconds minimum - Monitor logs: Check
outbox_log.csvregularly - Backup contacts: Keep a backup of your CSV before using
--update-contacts - Personalize content: Update templates for your specific use case
- Check spam folder: Monitor where your emails land
- Warm up gradually: Don't send hundreds of emails on day one
mailflow/
βββ send_mail_merge.py # Main application script
βββ .env.example # Example environment configuration
βββ subject.txt # Email subject template
βββ email_template_italiano.html # HTML email template
βββ contacts_hotels.csv # Sample contact list
βββ Makefile # Convenient make commands
βββ Dockerfile # Docker configuration
βββ README.md # This file
βββ LICENSE # MIT License
βββ .gitignore # Git ignore rules
βββ tests/
βββ test_render.py # Template rendering tests
βββ test_cli_dry_run.py # CLI functionality tests
This project is licensed under the MIT License - see the LICENSE file for details.
Gonzalo Medrano Ortiz
- GitHub: @gmodev
- Email: gonzalomeortiz@gmail.com
- Location: Italy
Contributions are welcome! Please feel free to submit issues or pull requests.
This tool is for legitimate business communication only. Users are responsible for:
- Complying with anti-spam laws (CAN-SPAM, GDPR, etc.)
- Obtaining proper consent when required
- Following email service provider terms of service
- Respecting recipients' privacy and preferences
Misuse of this tool for spam or unsolicited bulk email is strictly prohibited and may be illegal in your jurisdiction.
For issues or questions:
- Check this README first
- Review the troubleshooting section
- Check the issue tracker
- Create a new issue with details about your problem
Made with β€οΈ for professional job seekers