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 smtplib2from email.mime.text import MIMEText3from email.mime.multipart import MIMEMultipart45def send_simple_email(to_email, subject, body):6 """7 Send a simple text email.89 Args:10 to_email: Recipient email address11 subject: Email subject line12 body: Email body text13 """14 # Your email credentials15 sender_email = "your.email@gmail.com"16 app_password = "your-16-char-app-password" # Use environment variable in production!1718 # Create the email19 message = MIMEMultipart()20 message["From"] = sender_email21 message["To"] = to_email22 message["Subject"] = subject2324 # Attach the body25 message.attach(MIMEText(body, "plain"))2627 # Connect to Gmail and send28 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())3233 print(f"✅ Email sent to {to_email}")34 return True3536 except Exception as e:37 print(f"❌ Failed to send email: {e}")38 return False3940# Example usage41send_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.45 Args:6 to_email: Recipient email address7 subject: Email subject line8 html_content: HTML body content9 plain_text: Optional plain text alternative10 """11 sender_email = "your.email@gmail.com"12 app_password = "your-16-char-app-password"1314 message = MIMEMultipart("alternative")15 message["From"] = sender_email16 message["To"] = to_email17 message["Subject"] = subject1819 # Plain text version (for email clients that don't support HTML)20 if plain_text:21 message.attach(MIMEText(plain_text, "plain"))2223 # HTML version24 message.attach(MIMEText(html_content, "html"))2526 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())3031 print(f"✅ HTML email sent to {to_email}")32 return True3334 except Exception as e:35 print(f"❌ Failed to send email: {e}")36 return False3738# Example HTML email39html = """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>4546 <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>6061 <p>Best regards,<br>Alex</p>62</body>63</html>64"""6566send_html_email(67 to_email="team@company.com",68 subject="Weekly Status Report - Week 45",69 html_content=html70)
Step 4: Adding Attachments
Emails often need attachments—reports, invoices, images:
1from email.mime.base import MIMEBase2from email import encoders3import os45def send_email_with_attachment(to_email, subject, body, attachment_paths):6 """7 Send an email with one or more attachments.89 Args:10 to_email: Recipient email address11 subject: Email subject line12 body: Email body text13 attachment_paths: List of file paths to attach14 """15 sender_email = "your.email@gmail.com"16 app_password = "your-16-char-app-password"1718 message = MIMEMultipart()19 message["From"] = sender_email20 message["To"] = to_email21 message["Subject"] = subject2223 # Add body24 message.attach(MIMEText(body, "plain"))2526 # Add attachments27 for filepath in attachment_paths:28 if not os.path.exists(filepath):29 print(f"⚠️ Attachment not found: {filepath}")30 continue3132 filename = os.path.basename(filepath)3334 # Read and encode the file35 with open(filepath, "rb") as f:36 attachment = MIMEBase("application", "octet-stream")37 attachment.set_payload(f.read())3839 encoders.encode_base64(attachment)40 attachment.add_header(41 "Content-Disposition",42 f"attachment; filename={filename}"43 )4445 message.attach(attachment)46 print(f"📎 Attached: {filename}")4748 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())5253 print(f"✅ Email with attachments sent to {to_email}")54 return True5556 except Exception as e:57 print(f"❌ Failed to send email: {e}")58 return False5960# Example usage61send_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.45 Args:6 template_name: Name of the template7 **kwargs: Values to substitute into template89 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>2122 <h3>Highlights</h3>23 <ul>24 {highlights}25 </ul>2627 <h3>Metrics</h3>28 <p>Tasks Completed: <strong>{tasks_completed}</strong></p>29 <p>Hours Worked: <strong>{hours_worked}</strong></p>3031 <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>4344 <p>This is a friendly reminder that Invoice #{invoice_number}45 for <strong>${amount}</strong> is due on <strong>{due_date}</strong>.</p>4647 <p>If you've already sent payment, please disregard this message.</p>4849 <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>6263 <p>Thank you for meeting with me on {meeting_date} regarding {meeting_topic}.</p>6465 <h3>Action Items</h3>66 <ul>67 {action_items}68 </ul>6970 <h3>Next Steps</h3>71 <p>{next_steps}</p>7273 <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 }8081 if template_name not in templates:82 raise ValueError(f"Template '{template_name}' not found")8384 template = templates[template_name]8586 subject = template["subject"].format(**kwargs)87 html = template["html"].format(**kwargs)8889 return subject, html909192# Example usage93subject, 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)102103send_html_email("team@company.com", subject, html)
The Complete Script
1#!/usr/bin/env python32"""3Email Automation - Send professional emails with Python.4Author: Alex Rodriguez56This script sends emails with HTML formatting and attachments.7Supports templates for common email types.8"""910import os11import smtplib12from email import encoders13from email.mime.base import MIMEBase14from email.mime.multipart import MIMEMultipart15from email.mime.text import MIMEText16from datetime import datetime171819# ========================================20# CONFIGURATION21# ========================================2223# Email credentials - USE ENVIRONMENT VARIABLES IN PRODUCTION!24SMTP_SERVER = "smtp.gmail.com"25SMTP_PORT = 46526SENDER_EMAIL = os.environ.get("EMAIL_ADDRESS", "your.email@gmail.com")27SENDER_PASSWORD = os.environ.get("EMAIL_PASSWORD", "your-app-password")28SENDER_NAME = "Alex Rodriguez"293031# ========================================32# EMAIL TEMPLATES33# ========================================3435TEMPLATES = {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>4546 <div style="padding: 20px;">47 <p>Hi {recipient_name},</p>48 <p>{intro_text}</p>4950 {content}5152 <p style="margin-top: 30px;">Best regards,<br><strong>{sender_name}</strong></p>53 </div>5455 <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}808182def 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")919293# ========================================94# CORE EMAIL FUNCTIONS95# ========================================9697def create_email(to_email, subject, html_content, plain_text=None, attachments=None):98 """99 Create an email message with optional attachments.100101 Args:102 to_email: Recipient(s) - string or list103 subject: Email subject104 html_content: HTML body105 plain_text: Optional plain text alternative106 attachments: Optional list of file paths107108 Returns:109 MIMEMultipart message object110 """111 message = MIMEMultipart("mixed")112 message["From"] = f"{SENDER_NAME} <{SENDER_EMAIL}>"113 message["Subject"] = subject114115 # Handle multiple recipients116 if isinstance(to_email, list):117 message["To"] = ", ".join(to_email)118 else:119 message["To"] = to_email120121 # Create body container122 body = MIMEMultipart("alternative")123124 # Add plain text version125 if plain_text:126 body.attach(MIMEText(plain_text, "plain", "utf-8"))127128 # Add HTML version129 body.attach(MIMEText(html_content, "html", "utf-8"))130131 message.attach(body)132133 # Add attachments134 if attachments:135 for filepath in attachments:136 if not os.path.exists(filepath):137 print(f"⚠️ Attachment not found: {filepath}")138 continue139140 with open(filepath, "rb") as f:141 attachment = MIMEBase("application", "octet-stream")142 attachment.set_payload(f.read())143144 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)151152 return message153154155def send_email(message, to_email):156 """157 Send an email message.158159 Args:160 message: MIMEMultipart message object161 to_email: Recipient(s) - string or list162163 Returns:164 True if successful, False otherwise165 """166 recipients = to_email if isinstance(to_email, list) else [to_email]167168 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())172173 print(f"✅ Email sent to: {', '.join(recipients)}")174 return True175176 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}")182183 return False184185186def send_report(to_email, report_type, content, attachments=None, recipient_name="Team"):187 """188 Send a report email using the report template.189190 Args:191 to_email: Recipient email(s)192 report_type: Type of report (e.g., "Weekly", "Monthly")193 content: HTML content for the report body194 attachments: Optional list of file paths195 recipient_name: Name to address in email196 """197 template = TEMPLATES["report"]198199 subject = template["subject"].format(200 report_type=report_type,201 date=datetime.now().strftime("%B %d, %Y")202 )203204 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_NAME211 )212213 message = create_email(to_email, subject, html, attachments=attachments)214 return send_email(message, to_email)215216217def send_notification(to_email, title, message_text, priority="info", details=""):218 """219 Send a notification email.220221 Args:222 to_email: Recipient email(s)223 title: Notification title224 message_text: Main message225 priority: high, medium, low, or info226 details: Optional additional details (HTML)227 """228 template = TEMPLATES["notification"]229 color = get_priority_color(priority)230231 subject = template["subject"].format(232 priority=priority.upper(),233 title=title234 )235236 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 )243244 message = create_email(to_email, subject, html)245 return send_email(message, to_email)246247248def send_bulk_emails(recipients, subject, html_content, personalize_func=None):249 """250 Send emails to multiple recipients with optional personalization.251252 Args:253 recipients: List of dicts with 'email' and any personalization fields254 subject: Email subject255 html_content: HTML content (with {placeholders} for personalization)256 personalize_func: Optional function to customize content per recipient257258 Returns:259 Dict with success/failure counts260 """261 import time262263 results = {"sent": 0, "failed": 0}264265 for i, recipient in enumerate(recipients):266 email = recipient.get("email")267268 if not email:269 print(f"⚠️ Skipping recipient {i+1}: no email address")270 results["failed"] += 1271 continue272273 # Personalize content274 if personalize_func:275 personalized_html = personalize_func(html_content, recipient)276 else:277 personalized_html = html_content.format(**recipient)278279 message = create_email(email, subject, personalized_html)280281 if send_email(message, email):282 results["sent"] += 1283 else:284 results["failed"] += 1285286 # Rate limiting - don't spam the server287 if i < len(recipients) - 1:288 time.sleep(1)289290 print(f"\n📊 Bulk email results: {results['sent']} sent, {results['failed']} failed")291 return results292293294# ========================================295# MAIN296# ========================================297298def main():299 """Example usage of email automation functions."""300301 print("=" * 60)302 print("EMAIL AUTOMATION")303 print("=" * 60)304305 # Check configuration306 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 return311312 # Example 1: Send a simple report313 print("\n📧 Example 1: Sending report...")314315 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>322323 <h3>Next Week</h3>324 <p>Focus areas: Feature development, testing, documentation</p>325 """326327 send_report(328 to_email="recipient@example.com",329 report_type="Weekly",330 content=report_content,331 recipient_name="Team"332 )333334 # Example 2: Send a notification335 print("\n📧 Example 2: Sending notification...")336337 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 )344345 # Example 3: Bulk personalized emails346 print("\n📧 Example 3: Bulk emails (demo)...")347348 recipients = [349 {"email": "alice@example.com", "name": "Alice", "role": "Developer"},350 {"email": "bob@example.com", "name": "Bob", "role": "Designer"},351 ]352353 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 """361362 # Uncomment to actually send:363 # send_bulk_emails(recipients, "Personalized Update", html_template)364 print(" (Bulk email demo - uncomment to send)")365366 print("\n✅ Email automation examples complete!")367368369if __name__ == "__main__":370 main()
How to Run This Script
-
Set up Gmail App Password (see Step 1 above)
-
Set environment variables:
bash1# Linux/macOS2export EMAIL_ADDRESS="your.email@gmail.com"3export EMAIL_PASSWORD="your-16-char-app-password"45# Windows (Command Prompt)6set EMAIL_ADDRESS=your.email@gmail.com7set 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 3652SMTP_SERVER = "smtp.office365.com"3SMTP_PORT = 5874# Use STARTTLS instead of SSL:5# with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server:6# server.starttls()78# Yahoo9SMTP_SERVER = "smtp.mail.yahoo.com"10SMTP_PORT = 465
Add CC and BCC
1def create_email_with_cc(to, cc, bcc, subject, html):2 message = MIMEMultipart()3 message["To"] = to4 message["Cc"] = cc5 # BCC is not added to headers67 # 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 Monday2# Add to crontab: 0 9 * * 1 /usr/bin/python3 /path/to/email_automation.py
Common 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 imaplib2import email34def 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")910 _, messages = mail.search(None, "UNSEEN")1112 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']}")1718 mail.close()19 mail.logout()
Email Queue with Retry
1import json2from datetime import datetime34def 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 = []1112 email_data["queued_at"] = datetime.now().isoformat()13 email_data["attempts"] = 014 queue.append(email_data)1516 with open(queue_file, "w") as f:17 json.dump(queue, f)1819def process_queue(queue_file="email_queue.json", max_attempts=3):20 """Process queued emails with retry logic."""21 # Implementation for processing queue22 pass
Conclusion
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.
