Build a Python Email Automation System: Send 100 Personalized Emails in 5 Minutes
Manual email campaigns are slow and error-prone. Here's how to build an email automation system with Python that sends personalized emails at scale.
Basic Email Sender with Gmail
import smtplib
import ssl
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
import os
class EmailSender:
def __init__(self, email: str, password: str):
"""Initialize with Gmail credentials.
For Gmail: enable 2FA and create an App Password at
https://myaccount.google.com/apppasswords
"""
self.email = email
self.password = password
self.smtp_server = "smtp.gmail.com"
self.port = 587
def send_email(
self,
to_email: str,
subject: str,
body_text: str,
body_html: str = None,
attachments: list = None
) -> bool:
"""Send a single email with optional HTML and attachments."""
message = MIMEMultipart("alternative")
message["Subject"] = subject
message["From"] = self.email
message["To"] = to_email
# Add plain text part
message.attach(MIMEText(body_text, "plain"))
# Add HTML part if provided
if body_html:
message.attach(MIMEText(body_html, "html"))
# Add attachments if provided
if attachments:
for file_path in attachments:
with open(file_path, "rb") as f:
part = MIMEBase("application", "octet-stream")
part.set_payload(f.read())
encoders.encode_base64(part)
part.add_header(
"Content-Disposition",
f"attachment; filename= {os.path.basename(file_path)}"
)
message.attach(part)
# Send
context = ssl.create_default_context()
with smtplib.SMTP(self.smtp_server, self.port) as server:
server.ehlo()
server.starttls(context=context)
server.login(self.email, self.password)
server.sendmail(self.email, to_email, message.as_string())
return True
Personalized Campaign Sender
import csv
import time
from typing import Generator
def load_recipients(csv_path: str) -> Generator:
"""Load recipient data from CSV file."""
with open(csv_path, 'r') as f:
reader = csv.DictReader(f)
for row in reader:
yield row
def send_campaign(
sender: EmailSender,
csv_file: str,
subject_template: str,
body_template: str,
delay_seconds: float = 1.0
) -> dict:
"""Send personalized emails to a list of recipients."""
results = {"sent": 0, "failed": 0, "errors": []}
for recipient in load_recipients(csv_file):
# Personalize subject and body
subject = subject_template.format(**recipient)
body = body_template.format(**recipient)
try:
sender.send_email(
to_email=recipient["email"],
subject=subject,
body_text=body
)
results["sent"] += 1
print(f"✓ Sent to {recipient['email']}")
except Exception as e:
results["failed"] += 1
results["errors"].append({"email": recipient["email"], "error": str(e)})
print(f"✗ Failed to send to {recipient['email']}: {e}")
# Rate limiting to avoid spam filters
time.sleep(delay_seconds)
return results
# Usage
sender = EmailSender("your@gmail.com", "app_password_here")
# recipients.csv format: name,email,company,role
results = send_campaign(
sender=sender,
csv_file="recipients.csv",
subject_template="Hey {name}, quick question about {company}",
body_template="""Hi {name},
I noticed {company} is in the {role} space and thought you might find this useful.
[Your message here]
Best,
[Your name]
"""
)
print(f"Campaign complete: {results['sent']} sent, {results['failed']} failed")
HTML Email Template
Professional-looking emails with HTML:
HTML_TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
<style>
body {{ font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; }}
.header {{ background: #007bff; color: white; padding: 20px; }}
.content {{ padding: 20px; }}
.cta {{ background: #28a745; color: white; padding: 12px 24px;
text-decoration: none; border-radius: 5px; display: inline-block; }}
</style>
</head>
<body>
<div class="header">
<h1>Hello, {name}!</h1>
</div>
<div class="content">
<p>Thank you for your interest in {product}.</p>
<p>{message}</p>
<a href="{cta_url}" class="cta">{cta_text}</a>
</div>
</body>
</html>
"""
# Personalize the template for each recipient
html_body = HTML_TEMPLATE.format(
name="John",
product="Our Service",
message="We'd love to show you how we can help.",
cta_url="https://example.com",
cta_text="Learn More"
)
Track Open Rates
Add a tracking pixel to know who opened your emails:
import uuid
def add_tracking_pixel(html_body: str, tracking_server: str, email_id: str) -> str:
"""Add an invisible tracking pixel to HTML email."""
pixel_url = f"{tracking_server}/track/{email_id}.png"
pixel_html = f'<img src="{pixel_url}" width="1" height="1" style="display:none;">'
# Insert before closing </body> tag
return html_body.replace("</body>", f"{pixel_html}</body>")
# Each email gets a unique tracking ID
for recipient in recipients:
email_id = str(uuid.uuid4())
tracked_html = add_tracking_pixel(html_body, "https://track.yourdomain.com", email_id)
# Log email_id -> recipient mapping in your database
Real-World Performance
This system can process approximately 100 emails per 5 minutes when using 3-second delays (recommended to avoid spam folders).
For higher volume, consider using SendGrid, Mailgun, or AWS SES APIs instead of SMTP directly.
Want the Complete Automation Toolkit?
This email script is part of a larger automation toolkit I use for business.
👉 Get 50+ Python automation scripts — including email automation, web scrapers, invoice generators, data processors, and more.
Each script is documented and ready to customize for your needs.
Top comments (0)