How to Send 1000 Personalized Emails with 50 Lines of Python
You have a list of 1,000 contacts and a message to send. You open Mailchimp, stare at the pricing page, and realize that for your use case — a one-off outreach campaign, a product launch, or a customer notification — you'd be paying $20–$100/month for a tool you'll use twice.
There's a better way.
Python's built-in smtplib library can send personalized emails to your entire list with no monthly fees, no subscriber limits, and complete control over every message. Here's how to do it in under 50 lines of code.
Why Skip Mailchimp (for This Use Case)
Mailchimp is a great product — but it's designed for marketing teams managing ongoing campaigns with templates, A/B tests, and analytics dashboards. If you're a developer or technical founder who needs to:
- Send a one-time batch of personalized emails
- Trigger emails programmatically from your app
- Keep costs at zero for small/medium sends
- Own your sending logic without vendor lock-in
...then Python + SMTP is the right tool.
The tradeoffs: you handle deliverability yourself (use Gmail, Zoho, or a transactional provider like SendGrid's free tier), and you won't get open-rate tracking out of the box. For most programmatic use cases, that's a perfectly fine trade.
What You Need
- Python 3.x (no extra installs required —
smtplib,csv, andemailare all stdlib) - A Gmail account with App Passwords enabled (or any SMTP provider)
- A CSV file with your recipient list
Your CSV should look like this:
name,email,company
Alice Chen,alice@example.com,Acme Corp
Bob Smith,bob@example.com,TechStart
Carol Wu,carol@example.com,BuildCo
Part 1: Basic SMTP Email Sender
Start with the core pattern — authenticate and send a single email:
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
SMTP_SERVER = "smtp.gmail.com"
SMTP_PORT = 587
USERNAME = "you@gmail.com"
PASSWORD = "your-app-password" # Use Gmail App Password, not your login password
def send_email(to_name: str, to_email: str, subject: str, body: str) -> None:
msg = MIMEMultipart()
msg["From"] = USERNAME
msg["To"] = to_email
msg["Subject"] = subject
msg.attach(MIMEText(body, "plain"))
with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server:
server.starttls() # Upgrade to TLS
server.login(USERNAME, PASSWORD)
server.sendmail(USERNAME, to_email, msg.as_string())
print(f"✅ Sent to {to_name} <{to_email}>")
That's it. This function handles the full SMTP handshake: connect → TLS → authenticate → send → close.
Part 2: Loop Through a CSV and Personalize Each Email
Now wire it to your contacts list:
import csv
import time
def send_bulk_emails(csv_path: str) -> None:
subject = "A quick note from [Your Name]"
with open(csv_path, newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
name = row["name"]
email = row["email"]
company = row["company"]
# Personalize the message body per recipient
body = f"""Hi {name},
I came across {company} and wanted to reach out personally.
[Your personalized message here]
Best,
[Your Name]
"""
send_email(name, email, subject, body)
time.sleep(1) # Throttle: 1 email/second to avoid SMTP rate limits
send_bulk_emails("recipients.csv")
The time.sleep(1) is important — most SMTP providers rate-limit bulk sends. For 1,000 emails, this script runs in about 17 minutes unattended. Start it, go make coffee, come back to a done job.
Putting It All Together: The Full 50-Line Script
import smtplib, csv, time
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
SMTP_SERVER = "smtp.gmail.com"
SMTP_PORT = 587
USERNAME = "you@gmail.com"
PASSWORD = "your-app-password"
def send_email(to_name, to_email, subject, body):
msg = MIMEMultipart()
msg["From"] = USERNAME
msg["To"] = to_email
msg["Subject"] = subject
msg.attach(MIMEText(body, "plain"))
with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server:
server.starttls()
server.login(USERNAME, PASSWORD)
server.sendmail(USERNAME, to_email, msg.as_string())
def send_bulk_emails(csv_path):
subject = "Quick note for you"
sent, failed = 0, 0
with open(csv_path, newline="", encoding="utf-8") as f:
for row in csv.DictReader(f):
body = f"Hi {row['name']},\n\n[Your message here]\n\nBest,\n[Your Name]"
try:
send_email(row["name"], row["email"], subject, body)
print(f"✅ {row['email']}")
sent += 1
except Exception as e:
print(f"❌ {row['email']} — {e}")
failed += 1
time.sleep(1)
print(f"\nDone: {sent} sent, {failed} failed")
send_bulk_emails("recipients.csv")
Under 50 lines. No dependencies. Runs anywhere Python runs.
Tips for Staying Out of Spam
- Use a warmed-up address — don't blast 1,000 emails from a brand-new Gmail on day one
- Personalize every email — identical body = spam filter magnet
- Add an unsubscribe line — "Reply STOP to unsubscribe" is enough for small lists
- Throttle your sends — 60–100/hour is safe for Gmail free accounts
- Use Gmail App Passwords — never hardcode your real password; generate one at myaccount.google.com > Security > App Passwords
Want a Pre-Built Version with Error Handling, Logging & Retry?
If you'd rather skip the setup and get a production-ready script that includes:
- Automatic retry on failed sends
- Send log with timestamps
- HTML email support
- Progress bar for large lists
Check out the ready-to-run version here:
Python Email Automation Script — Bulk + Personalized
For $25, you get a complete tool you can run today — no Mailchimp account required.
Your list, your code, your control.
Top comments (0)