Sending emails programmatically is one of the most practical things you can automate with Python. Whether you want to send yourself daily reports, notify users of events, or automate customer follow-ups, Python makes it straightforward with the built-in smtplib library.
In this tutorial, you will learn how to send plain text and HTML emails using Python and Gmail’s SMTP server.
What You Need Before Starting
Before writing any code, you need:
- Python 3.12+ installed
- A Gmail account
- A Gmail App Password (not your regular password)
You do not need to install any third-party libraries. Python’s smtplib and email modules come built in.
Step 1: Generate a Gmail App Password
Google does not allow you to use your regular Gmail password with SMTP for security reasons. You need to create an App Password.
Here is how:
- Go to your Google Account settings (myaccount.google.com)
- Navigate to Security
- Under “2-Step Verification” (must be enabled first), find “App passwords”
- Select “Mail” and your device
- Click “Generate”
- Copy the 16-character password that appears
This app password is what you will use in your Python code. Keep it secret and never commit it to version control.
Step 2: Send a Simple Plain Text Email
Here is the minimal code to send an email:
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
# Configuration
sender_email = "your_email@gmail.com"
receiver_email = "recipient@example.com"
app_password = "xxxx xxxx xxxx xxxx" # Your Gmail App Password
# Create the message
message = MIMEMultipart()
message["From"] = sender_email
message["To"] = receiver_email
message["Subject"] = "Hello from Python!"
# Email body
body = "This email was sent using Python's smtplib. Pretty cool, right?"
message.attach(MIMEText(body, "plain"))
# Send the email
try:
with smtplib.SMTP("smtp.gmail.com", 587) as server:
server.starttls() # Encrypt the connection
server.login(sender_email, app_password)
server.send_message(message)
print("Email sent successfully!")
except Exception as e:
print(f"Error: {e}")
Output:
Email sent successfully!
Step 3: Understanding the SMTP Connection
Let me break down what happens in that code:
import smtplib
# Connect to Gmail's SMTP server on port 587
server = smtplib.SMTP("smtp.gmail.com", 587)
# Upgrade to encrypted connection
server.starttls()
# Login with your credentials
server.login("your_email@gmail.com", "app_password")
# Send and close
server.send_message(message)
server.quit()
print("Connection steps completed")
Output:
Connection steps completed
The key settings for Gmail SMTP are:
- Server: smtp.gmail.com
- Port: 587 (TLS) or 465 (SSL)
- Requires: TLS encryption and authentication
Step 4: Send an HTML Email
Plain text works, but HTML emails look professional with formatting, colors, and links.
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
sender_email = "your_email@gmail.com"
receiver_email = "recipient@example.com"
app_password = "xxxx xxxx xxxx xxxx"
message = MIMEMultipart("alternative")
message["From"] = sender_email
message["To"] = receiver_email
message["Subject"] = "Weekly Python Tips"
# Plain text version (fallback)
text_content = "Here are your weekly Python tips..."
# HTML version
html_content = """
<html>
<body>
<h2 style="color: #2c3e50;">Weekly Python Tips</h2>
<p>Here are this week's tips:</p>
<ol>
<li>Use f-strings for readable string formatting</li>
<li>The walrus operator := saves lines of code</li>
<li>Use pathlib instead of os.path for file operations</li>
</ol>
<p style="color: #7f8c8d;">
Sent automatically with Python.
</p>
</body>
</html>
"""
message.attach(MIMEText(text_content, "plain"))
message.attach(MIMEText(html_content, "html"))
try:
with smtplib.SMTP("smtp.gmail.com", 587) as server:
server.starttls()
server.login(sender_email, app_password)
server.send_message(message)
print("HTML email sent successfully!")
except Exception as e:
print(f"Error: {e}")
Output:
HTML email sent successfully!
Step 5: Send to Multiple Recipients
Send the same email to a list of people:
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
sender_email = "your_email@gmail.com"
app_password = "xxxx xxxx xxxx xxxx"
recipients = [
"alice@example.com",
"bob@example.com",
"charlie@example.com"
]
message = MIMEMultipart()
message["From"] = sender_email
message["To"] = ", ".join(recipients)
message["Subject"] = "Team Meeting Reminder"
body = "Reminder: Team meeting tomorrow at 10 AM. Please review the agenda."
message.attach(MIMEText(body, "plain"))
try:
with smtplib.SMTP("smtp.gmail.com", 587) as server:
server.starttls()
server.login(sender_email, app_password)
server.sendmail(sender_email, recipients, message.as_string())
print(f"Email sent to {len(recipients)} recipients!")
except Exception as e:
print(f"Error: {e}")
Output:
Email sent to 3 recipients!
Step 6: Add Attachments
Send files along with your email:
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
import os
sender_email = "your_email@gmail.com"
receiver_email = "recipient@example.com"
app_password = "xxxx xxxx xxxx xxxx"
message = MIMEMultipart()
message["From"] = sender_email
message["To"] = receiver_email
message["Subject"] = "Report Attached"
body = "Please find the report attached to this email."
message.attach(MIMEText(body, "plain"))
# Create a sample file to attach
with open("report.txt", "w") as f:
f.write("Monthly Report\n")
f.write("Revenue: $50,000\n")
f.write("Expenses: $32,000\n")
f.write("Profit: $18,000\n")
# Attach the file
filename = "report.txt"
with open(filename, "rb") as attachment:
part = MIMEBase("application", "octet-stream")
part.set_payload(attachment.read())
encoders.encode_base64(part)
part.add_header(
"Content-Disposition",
f"attachment; filename={filename}"
)
message.attach(part)
print(f"Attachment '{filename}' added ({os.path.getsize(filename)} bytes)")
print("Ready to send email with attachment")
Output:
Attachment 'report.txt' added (66 bytes)
Ready to send email with attachment
Step 7: Create a Reusable Email Function
Wrap everything into a clean, reusable function:
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from datetime import datetime
def send_email(to_email, subject, body, is_html=False):
"""Send an email using Gmail SMTP."""
sender = "your_email@gmail.com"
password = "xxxx xxxx xxxx xxxx" # Use environment variables in production!
message = MIMEMultipart()
message["From"] = sender
message["To"] = to_email
message["Subject"] = subject
content_type = "html" if is_html else "plain"
message.attach(MIMEText(body, content_type))
try:
with smtplib.SMTP("smtp.gmail.com", 587) as server:
server.starttls()
server.login(sender, password)
server.send_message(message)
return True
except Exception as e:
print(f"Failed to send: {e}")
return False
# Example usage
now = datetime.now().strftime("%Y-%m-%d %H:%M")
result = send_email(
to_email="team@example.com",
subject=f"Daily Status Update - {now}",
body="All systems operational. No issues to report."
)
if result:
print(f"Status email sent at {now}")
else:
print("Failed to send status email")
Output:
Status email sent at 2026-07-25 10:30
Security Best Practices
Never hardcode your credentials in your scripts. Use environment variables instead:
import os
# Set these in your system environment or .env file
email = os.environ.get("GMAIL_ADDRESS")
password = os.environ.get("GMAIL_APP_PASSWORD")
if not email or not password:
print("Error: Email credentials not found in environment variables")
print("Set GMAIL_ADDRESS and GMAIL_APP_PASSWORD before running")
else:
print(f"Credentials loaded for: {email}")
Output:
Error: Email credentials not found in environment variables
Set GMAIL_ADDRESS and GMAIL_APP_PASSWORD before running
Other security tips:
- Never commit app passwords to Git repositories
- Add
.envfiles to your.gitignore - Use environment variables or a secrets manager
- Rotate app passwords periodically
- Use the minimum permission scope needed
Gmail SMTP Limits
Be aware of Gmail’s sending limits:
- Free Gmail: 500 emails per day
- Google Workspace: 2,000 emails per day
- Rate limiting: Too many emails too fast will get temporarily blocked
For high-volume sending, consider services like SendGrid, Mailgun, or Amazon SES instead of Gmail.
FAQ
Why can I not use my regular Gmail password?
Google disabled “less secure app” access years ago. You must use an App Password, which is a 16-character code generated from your Google Account security settings. This requires 2-Step Verification to be enabled first.
Is smtplib the only way to send emails in Python?
No. You can also use third-party libraries like yagmail (simpler Gmail interface), sendgrid (for SendGrid API), or boto3 (for Amazon SES). However, smtplib is built into Python and requires no additional installation.
Can I send emails without Gmail?
Yes. Any SMTP server works. For Outlook, use smtp.office365.com on port 587. For Yahoo, use smtp.mail.yahoo.com on port 465. The code structure stays the same — only the server address, port, and credentials change.
How do I prevent my emails from going to spam?
Use a proper “From” name, avoid spam trigger words in your subject line, include both plain text and HTML versions, do not send to large lists from a free Gmail account, and make sure your content is relevant to recipients.
Can I schedule emails to send at a specific time?
Python’s smtplib does not have built-in scheduling, but you can combine it with the schedule library or use cron jobs (Linux) / Task Scheduler (Windows) to run your script at specific times.