Automate Email Sending with Python: Complete Guide with Attachments
You send the same types of emails every week. Status reports to your team. Invoices to clients. Reminders to stakeholders. Each one takes a few minutes—minutes that add up to hours.
Let Python send your emails while you focus on work that actually matters.
What You'll Learn
- Sending emails with Python's built-in libraries
- Using Gmail and other email providers
- Adding HTML formatting for professional emails
- Attaching files (PDFs, Excel, images)
- Creating email templates for reuse
Prerequisites
- Python 3.8 or higher
- An email account (Gmail recommended for this tutorial)
- App Password for Gmail (we'll show you how to set this up)
The Problem
Manual email tasks that eat your time:
- Weekly status reports to multiple recipients
- Sending invoices or documents to clients
- Reminder emails on schedules
- Bulk notifications to teams
- Personalizing repetitive emails
The Solution
A Python script that:
- Connects to your email provider
- Composes professional emails (plain text or HTML)
- Attaches files when needed
- Sends to one or multiple recipients
- Can be scheduled to run automatically
Step 1: Setting Up Gmail App Password
For security, Gmail requires an "App Password" instead of your regular password:
- Go to your Google Account (myaccount.google.com)
- Select Security on the left
- Under "Signing in to Google," select 2-Step Verification (enable if not already)
- At the bottom, select App passwords
- Select app: "Mail" and device: "Other (Custom name)"
- Enter a name like "Python Email Script"
- Click Generate
- Copy the 16-character password (you'll only see it once!)
Important: Store this password securely, never in your code.
Step 2: Sending a Simple Email
Let's start with the basics:
1import smtplib
2from email.mime.text import MIMEText
3from email.mime.multipart import MIMEMultipart
4
5def send_simple_email(to_email, subject, body):
6 """
7 Send a simple text email.
8
9 Args:
10 to_email: Recipient email address
11 subject: Email subject line
12 body: Email body text
13 """
14 # Your email credentials
15 sender_email = "your.email@gmail.com"
16 app_password = "your-16-char-app-password" # Use environment variable in production!
17
18 # Create the email
19 message = MIMEMultipart()
20 message["From"] = sender_email
21 message["To"] = to_email
22 message["Subject"] = subject
23
24 # Attach the body
25 message.attach(MIMEText(body, "plain"))
26
27 # Connect to Gmail and send
28 try:
29 with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
30 server.login(sender_email, app_password)
31 server.sendmail(sender_email, to_email, message.as_string())
32
33 print(f"✅ Email sent to {to_email}")
34 return True
35
36 except Exception as e:
37 print(f"❌ Failed to send email: {e}")
38 return False
39
40# Example usage
41send_simple_email(
42 to_email="colleague@company.com",
43 subject="Quick Update",
44 body="Hi,\n\nJust wanted to share a quick update on the project.\n\nBest regards"
45)Step 3: Sending HTML Emails
Plain text is fine, but HTML emails look professional:
1def send_html_email(to_email, subject, html_content, plain_text=None):
2 """
3 Send an HTML formatted email with plain text fallback.
4
5 Args:
6 to_email: Recipient email address
7 subject: Email subject line
8 html_content: HTML body content
9 plain_text: Optional plain text alternative
10 """
11 sender_email = "your.email@gmail.com"
12 app_password = "your-16-char-app-password"
13
14 message = MIMEMultipart("alternative")
15 message["From"] = sender_email
16 message["To"] = to_email
17 message["Subject"] = subject
18
19 # Plain text version (for email clients that don't support HTML)
20 if plain_text:
21 message.attach(MIMEText(plain_text, "plain"))
22
23 # HTML version
24 message.attach(MIMEText(html_content, "html"))
25
26 try:
27 with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
28 server.login(sender_email, app_password)
29 server.sendmail(sender_email, to_email, message.as_string())
30
31 print(f"✅ HTML email sent to {to_email}")
32 return True
33
34 except Exception as e:
35 print(f"❌ Failed to send email: {e}")
36 return False
37
38# Example HTML email
39html = """
40<html>
41<body style="font-family: Arial, sans-serif; line-height: 1.6;">
42 <h2 style="color: #333;">Weekly Status Report</h2>
43 <p>Hi Team,</p>
44 <p>Here's the update for this week:</p>
45
46 <table style="border-collapse: collapse; width: 100%;">
47 <tr style="background-color: #4472C4; color: white;">
48 <th style="padding: 10px; border: 1px solid #ddd;">Task</th>
49 <th style="padding: 10px; border: 1px solid #ddd;">Status</th>
50 </tr>
51 <tr>
52 <td style="padding: 10px; border: 1px solid #ddd;">Feature Development</td>
53 <td style="padding: 10px; border: 1px solid #ddd;">✅ Complete</td>
54 </tr>
55 <tr>
56 <td style="padding: 10px; border: 1px solid #ddd;">Testing</td>
57 <td style="padding: 10px; border: 1px solid #ddd;">🔄 In Progress</td>
58 </tr>
59 </table>
60
61 <p>Best regards,<br>Alex</p>
62</body>
63</html>
64"""
65
66send_html_email(
67 to_email="team@company.com",
68 subject="Weekly Status Report - Week 45",
69 html_content=html
70)Step 4: Adding Attachments
Emails often need attachments—reports, invoices, images:
1from email.mime.base import MIMEBase
2from email import encoders
3import os
4
5def send_email_with_attachment(to_email, subject, body, attachment_paths):
6 """
7 Send an email with one or more attachments.
8
9 Args:
10 to_email: Recipient email address
11 subject: Email subject line
12 body: Email body text
13 attachment_paths: List of file paths to attach
14 """
15 sender_email = "your.email@gmail.com"
16 app_password = "your-16-char-app-password"
17
18 message = MIMEMultipart()
19 message["From"] = sender_email
20 message["To"] = to_email
21 message["Subject"] = subject
22
23 # Add body
24 message.attach(MIMEText(body, "plain"))
25
26 # Add attachments
27 for filepath in attachment_paths:
28 if not os.path.exists(filepath):
29 print(f"⚠️ Attachment not found: {filepath}")
30 continue
31
32 filename = os.path.basename(filepath)
33
34 # Read and encode the file
35 with open(filepath, "rb") as f:
36 attachment = MIMEBase("application", "octet-stream")
37 attachment.set_payload(f.read())
38
39 encoders.encode_base64(attachment)
40 attachment.add_header(
41 "Content-Disposition",
42 f"attachment; filename={filename}"
43 )
44
45 message.attach(attachment)
46 print(f"📎 Attached: {filename}")
47
48 try:
49 with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
50 server.login(sender_email, app_password)
51 server.sendmail(sender_email, to_email, message.as_string())
52
53 print(f"✅ Email with attachments sent to {to_email}")
54 return True
55
56 except Exception as e:
57 print(f"❌ Failed to send email: {e}")
58 return False
59
60# Example usage
61send_email_with_attachment(
62 to_email="client@company.com",
63 subject="Monthly Report - November 2025",
64 body="Hi,\n\nPlease find the monthly report attached.\n\nBest regards",
65 attachment_paths=["./reports/november_2025.pdf", "./reports/summary.xlsx"]
66)Step 5: Using Email Templates
Create reusable templates for common emails:
1def load_template(template_name, **kwargs):
2 """
3 Load an email template and fill in placeholders.
4
5 Args:
6 template_name: Name of the template
7 **kwargs: Values to substitute into template
8
9 Returns:
10 Tuple of (subject, html_content)
11 """
12 templates = {
13 "weekly_report": {
14 "subject": "Weekly Report - {week_date}",
15 "html": """
16 <html>
17 <body style="font-family: Arial, sans-serif;">
18 <h2>Weekly Report</h2>
19 <p>Hi {recipient_name},</p>
20 <p>Here's the weekly summary for {week_date}:</p>
21
22 <h3>Highlights</h3>
23 <ul>
24 {highlights}
25 </ul>
26
27 <h3>Metrics</h3>
28 <p>Tasks Completed: <strong>{tasks_completed}</strong></p>
29 <p>Hours Worked: <strong>{hours_worked}</strong></p>
30
31 <p>Best regards,<br>{sender_name}</p>
32 </body>
33 </html>
34 """
35 },
36 "invoice_reminder": {
37 "subject": "Invoice #{invoice_number} - Payment Reminder",
38 "html": """
39 <html>
40 <body style="font-family: Arial, sans-serif;">
41 <h2>Payment Reminder</h2>
42 <p>Dear {client_name},</p>
43
44 <p>This is a friendly reminder that Invoice #{invoice_number}
45 for <strong>${amount}</strong> is due on <strong>{due_date}</strong>.</p>
46
47 <p>If you've already sent payment, please disregard this message.</p>
48
49 <p>Thank you for your business!</p>
50 <p>Best regards,<br>{company_name}</p>
51 </body>
52 </html>
53 """
54 },
55 "meeting_followup": {
56 "subject": "Follow-up: {meeting_topic}",
57 "html": """
58 <html>
59 <body style="font-family: Arial, sans-serif;">
60 <h2>Meeting Follow-up</h2>
61 <p>Hi {recipient_name},</p>
62
63 <p>Thank you for meeting with me on {meeting_date} regarding {meeting_topic}.</p>
64
65 <h3>Action Items</h3>
66 <ul>
67 {action_items}
68 </ul>
69
70 <h3>Next Steps</h3>
71 <p>{next_steps}</p>
72
73 <p>Please let me know if you have any questions.</p>
74 <p>Best regards,<br>{sender_name}</p>
75 </body>
76 </html>
77 """
78 }
79 }
80
81 if template_name not in templates:
82 raise ValueError(f"Template '{template_name}' not found")
83
84 template = templates[template_name]
85
86 subject = template["subject"].format(**kwargs)
87 html = template["html"].format(**kwargs)
88
89 return subject, html
90
91
92# Example usage
93subject, html = load_template(
94 "weekly_report",
95 recipient_name="Team",
96 week_date="November 10-14, 2025",
97 highlights="<li>Completed feature X</li><li>Fixed 12 bugs</li><li>Onboarded new team member</li>",
98 tasks_completed=25,
99 hours_worked=42,
100 sender_name="Alex"
101)
102
103send_html_email("team@company.com", subject, html)The Complete Script
1#!/usr/bin/env python3
2"""
3Email Automation - Send professional emails with Python.
4Author: Alex Rodriguez
5
6This script sends emails with HTML formatting and attachments.
7Supports templates for common email types.
8"""
9
10import os
11import smtplib
12from email import encoders
13from email.mime.base import MIMEBase
14from email.mime.multipart import MIMEMultipart
15from email.mime.text import MIMEText
16from datetime import datetime
17
18
19# ========================================
20# CONFIGURATION
21# ========================================
22
23# Email credentials - USE ENVIRONMENT VARIABLES IN PRODUCTION!
24SMTP_SERVER = "smtp.gmail.com"
25SMTP_PORT = 465
26SENDER_EMAIL = os.environ.get("EMAIL_ADDRESS", "your.email@gmail.com")
27SENDER_PASSWORD = os.environ.get("EMAIL_PASSWORD", "your-app-password")
28SENDER_NAME = "Alex Rodriguez"
29
30
31# ========================================
32# EMAIL TEMPLATES
33# ========================================
34
35TEMPLATES = {
36 "report": {
37 "subject": "{report_type} Report - {date}",
38 "html": """
39 <html>
40 <body style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
41 <div style="background-color: #4472C4; color: white; padding: 20px; text-align: center;">
42 <h1 style="margin: 0;">{report_type} Report</h1>
43 <p style="margin: 5px 0 0 0;">{date}</p>
44 </div>
45
46 <div style="padding: 20px;">
47 <p>Hi {recipient_name},</p>
48 <p>{intro_text}</p>
49
50 {content}
51
52 <p style="margin-top: 30px;">Best regards,<br><strong>{sender_name}</strong></p>
53 </div>
54
55 <div style="background-color: #f5f5f5; padding: 10px; text-align: center; font-size: 12px; color: #666;">
56 This is an automated message. Please do not reply directly.
57 </div>
58 </body>
59 </html>
60 """
61 },
62 "notification": {
63 "subject": "[{priority}] {title}",
64 "html": """
65 <html>
66 <body style="font-family: Arial, sans-serif;">
67 <div style="border-left: 4px solid {color}; padding-left: 15px;">
68 <h2 style="color: {color};">{title}</h2>
69 <p>{message}</p>
70 {details}
71 </div>
72 <p style="color: #666; font-size: 12px; margin-top: 30px;">
73 Sent at {timestamp}
74 </p>
75 </body>
76 </html>
77 """
78 }
79}
80
81
82def get_priority_color(priority):
83 """Get color based on priority level."""
84 colors = {
85 "high": "#dc3545",
86 "medium": "#ffc107",
87 "low": "#28a745",
88 "info": "#17a2b8"
89 }
90 return colors.get(priority.lower(), "#6c757d")
91
92
93# ========================================
94# CORE EMAIL FUNCTIONS
95# ========================================
96
97def create_email(to_email, subject, html_content, plain_text=None, attachments=None):
98 """
99 Create an email message with optional attachments.
100
101 Args:
102 to_email: Recipient(s) - string or list
103 subject: Email subject
104 html_content: HTML body
105 plain_text: Optional plain text alternative
106 attachments: Optional list of file paths
107
108 Returns:
109 MIMEMultipart message object
110 """
111 message = MIMEMultipart("mixed")
112 message["From"] = f"{SENDER_NAME} <{SENDER_EMAIL}>"
113 message["Subject"] = subject
114
115 # Handle multiple recipients
116 if isinstance(to_email, list):
117 message["To"] = ", ".join(to_email)
118 else:
119 message["To"] = to_email
120
121 # Create body container
122 body = MIMEMultipart("alternative")
123
124 # Add plain text version
125 if plain_text:
126 body.attach(MIMEText(plain_text, "plain", "utf-8"))
127
128 # Add HTML version
129 body.attach(MIMEText(html_content, "html", "utf-8"))
130
131 message.attach(body)
132
133 # Add attachments
134 if attachments:
135 for filepath in attachments:
136 if not os.path.exists(filepath):
137 print(f"⚠️ Attachment not found: {filepath}")
138 continue
139
140 with open(filepath, "rb") as f:
141 attachment = MIMEBase("application", "octet-stream")
142 attachment.set_payload(f.read())
143
144 encoders.encode_base64(attachment)
145 filename = os.path.basename(filepath)
146 attachment.add_header(
147 "Content-Disposition",
148 f"attachment; filename={filename}"
149 )
150 message.attach(attachment)
151
152 return message
153
154
155def send_email(message, to_email):
156 """
157 Send an email message.
158
159 Args:
160 message: MIMEMultipart message object
161 to_email: Recipient(s) - string or list
162
163 Returns:
164 True if successful, False otherwise
165 """
166 recipients = to_email if isinstance(to_email, list) else [to_email]
167
168 try:
169 with smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT) as server:
170 server.login(SENDER_EMAIL, SENDER_PASSWORD)
171 server.sendmail(SENDER_EMAIL, recipients, message.as_string())
172
173 print(f"✅ Email sent to: {', '.join(recipients)}")
174 return True
175
176 except smtplib.SMTPAuthenticationError:
177 print("❌ Authentication failed. Check your email and app password.")
178 except smtplib.SMTPException as e:
179 print(f"❌ SMTP error: {e}")
180 except Exception as e:
181 print(f"❌ Failed to send email: {e}")
182
183 return False
184
185
186def send_report(to_email, report_type, content, attachments=None, recipient_name="Team"):
187 """
188 Send a report email using the report template.
189
190 Args:
191 to_email: Recipient email(s)
192 report_type: Type of report (e.g., "Weekly", "Monthly")
193 content: HTML content for the report body
194 attachments: Optional list of file paths
195 recipient_name: Name to address in email
196 """
197 template = TEMPLATES["report"]
198
199 subject = template["subject"].format(
200 report_type=report_type,
201 date=datetime.now().strftime("%B %d, %Y")
202 )
203
204 html = template["html"].format(
205 report_type=report_type,
206 date=datetime.now().strftime("%B %d, %Y"),
207 recipient_name=recipient_name,
208 intro_text="Please find the latest report below.",
209 content=content,
210 sender_name=SENDER_NAME
211 )
212
213 message = create_email(to_email, subject, html, attachments=attachments)
214 return send_email(message, to_email)
215
216
217def send_notification(to_email, title, message_text, priority="info", details=""):
218 """
219 Send a notification email.
220
221 Args:
222 to_email: Recipient email(s)
223 title: Notification title
224 message_text: Main message
225 priority: high, medium, low, or info
226 details: Optional additional details (HTML)
227 """
228 template = TEMPLATES["notification"]
229 color = get_priority_color(priority)
230
231 subject = template["subject"].format(
232 priority=priority.upper(),
233 title=title
234 )
235
236 html = template["html"].format(
237 color=color,
238 title=title,
239 message=message_text,
240 details=details,
241 timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
242 )
243
244 message = create_email(to_email, subject, html)
245 return send_email(message, to_email)
246
247
248def send_bulk_emails(recipients, subject, html_content, personalize_func=None):
249 """
250 Send emails to multiple recipients with optional personalization.
251
252 Args:
253 recipients: List of dicts with 'email' and any personalization fields
254 subject: Email subject
255 html_content: HTML content (with {placeholders} for personalization)
256 personalize_func: Optional function to customize content per recipient
257
258 Returns:
259 Dict with success/failure counts
260 """
261 import time
262
263 results = {"sent": 0, "failed": 0}
264
265 for i, recipient in enumerate(recipients):
266 email = recipient.get("email")
267
268 if not email:
269 print(f"⚠️ Skipping recipient {i+1}: no email address")
270 results["failed"] += 1
271 continue
272
273 # Personalize content
274 if personalize_func:
275 personalized_html = personalize_func(html_content, recipient)
276 else:
277 personalized_html = html_content.format(**recipient)
278
279 message = create_email(email, subject, personalized_html)
280
281 if send_email(message, email):
282 results["sent"] += 1
283 else:
284 results["failed"] += 1
285
286 # Rate limiting - don't spam the server
287 if i < len(recipients) - 1:
288 time.sleep(1)
289
290 print(f"\n📊 Bulk email results: {results['sent']} sent, {results['failed']} failed")
291 return results
292
293
294# ========================================
295# MAIN
296# ========================================
297
298def main():
299 """Example usage of email automation functions."""
300
301 print("=" * 60)
302 print("EMAIL AUTOMATION")
303 print("=" * 60)
304
305 # Check configuration
306 if SENDER_PASSWORD == "your-app-password":
307 print("\n⚠️ Please configure your email credentials!")
308 print(" Set EMAIL_ADDRESS and EMAIL_PASSWORD environment variables")
309 print(" Or update the SENDER_EMAIL and SENDER_PASSWORD constants")
310 return
311
312 # Example 1: Send a simple report
313 print("\n📧 Example 1: Sending report...")
314
315 report_content = """
316 <h3>Summary</h3>
317 <ul>
318 <li>Tasks completed: 15</li>
319 <li>Hours logged: 40</li>
320 <li>Issues resolved: 8</li>
321 </ul>
322
323 <h3>Next Week</h3>
324 <p>Focus areas: Feature development, testing, documentation</p>
325 """
326
327 send_report(
328 to_email="recipient@example.com",
329 report_type="Weekly",
330 content=report_content,
331 recipient_name="Team"
332 )
333
334 # Example 2: Send a notification
335 print("\n📧 Example 2: Sending notification...")
336
337 send_notification(
338 to_email="recipient@example.com",
339 title="Deployment Complete",
340 message_text="Version 2.5.0 has been successfully deployed to production.",
341 priority="info",
342 details="<p>Changes: Bug fixes, performance improvements</p>"
343 )
344
345 # Example 3: Bulk personalized emails
346 print("\n📧 Example 3: Bulk emails (demo)...")
347
348 recipients = [
349 {"email": "alice@example.com", "name": "Alice", "role": "Developer"},
350 {"email": "bob@example.com", "name": "Bob", "role": "Designer"},
351 ]
352
353 html_template = """
354 <html>
355 <body>
356 <h2>Hello {name}!</h2>
357 <p>This is a personalized message for our {role}.</p>
358 </body>
359 </html>
360 """
361
362 # Uncomment to actually send:
363 # send_bulk_emails(recipients, "Personalized Update", html_template)
364 print(" (Bulk email demo - uncomment to send)")
365
366 print("\n✅ Email automation examples complete!")
367
368
369if __name__ == "__main__":
370 main()How to Run This Script
-
Set up Gmail App Password (see Step 1 above)
-
Set environment variables:
bash1# Linux/macOS 2export EMAIL_ADDRESS="your.email@gmail.com" 3export EMAIL_PASSWORD="your-16-char-app-password" 4 5# Windows (Command Prompt) 6set EMAIL_ADDRESS=your.email@gmail.com 7set EMAIL_PASSWORD=your-16-char-app-password -
Save the script as
email_automation.py -
Run the script:
bash1python email_automation.py
Customization Options
Use Other Email Providers
1# Outlook/Office 365
2SMTP_SERVER = "smtp.office365.com"
3SMTP_PORT = 587
4# Use STARTTLS instead of SSL:
5# with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server:
6# server.starttls()
7
8# Yahoo
9SMTP_SERVER = "smtp.mail.yahoo.com"
10SMTP_PORT = 465Add CC and BCC
1def create_email_with_cc(to, cc, bcc, subject, html):
2 message = MIMEMultipart()
3 message["To"] = to
4 message["Cc"] = cc
5 # BCC is not added to headers
6
7 # When sending, include all recipients:
8 all_recipients = [to] + cc.split(",") + bcc.split(",")
9 server.sendmail(SENDER_EMAIL, all_recipients, message.as_string())Schedule Emails
Combine with task scheduling:
1# Run at 9 AM every Monday
2# Add to crontab: 0 9 * * 1 /usr/bin/python3 /path/to/email_automation.pyCommon Issues & Solutions
| Issue | Solution |
|---|---|
| Authentication failed | Check app password; ensure 2FA is enabled |
| Connection timeout | Check firewall; try different port |
| Emails going to spam | Use proper HTML; avoid spam trigger words |
| Attachments not showing | Check file paths; verify files exist |
| Special characters broken | Use UTF-8 encoding for MIMEText |
Taking It Further
Read Replies with IMAP
1import imaplib
2import email
3
4def check_inbox():
5 """Read emails from inbox."""
6 mail = imaplib.IMAP4_SSL("imap.gmail.com")
7 mail.login(SENDER_EMAIL, SENDER_PASSWORD)
8 mail.select("inbox")
9
10 _, messages = mail.search(None, "UNSEEN")
11
12 for num in messages[0].split():
13 _, data = mail.fetch(num, "(RFC822)")
14 msg = email.message_from_bytes(data[0][1])
15 print(f"From: {msg['from']}")
16 print(f"Subject: {msg['subject']}")
17
18 mail.close()
19 mail.logout()Email Queue with Retry
1import json
2from datetime import datetime
3
4def queue_email(email_data, queue_file="email_queue.json"):
5 """Add email to send queue."""
6 try:
7 with open(queue_file) as f:
8 queue = json.load(f)
9 except FileNotFoundError:
10 queue = []
11
12 email_data["queued_at"] = datetime.now().isoformat()
13 email_data["attempts"] = 0
14 queue.append(email_data)
15
16 with open(queue_file, "w") as f:
17 json.dump(queue, f)
18
19def process_queue(queue_file="email_queue.json", max_attempts=3):
20 """Process queued emails with retry logic."""
21 # Implementation for processing queue
22 passConclusion
You now have a complete email automation system. Send reports, notifications, and personalized bulk emails—all with professional HTML formatting and attachments.
The key is the template system. Once you create templates for your common email types, sending them becomes a single function call. Combine this with scheduling, and your routine email tasks happen automatically.
Start with one email you send regularly. Automate it. See how much time you save. Then expand to more. Before long, you'll wonder how you ever managed without it.
Your inbox, automated.
Sponsored Content
Interested in advertising? Reach automation professionals through our platform.