Python's smtplib module lets you send emails via SMTP servers. Combined with the email module for message construction, you can send plain text, HTML, and attachments.

Basic Email

import smtplib
from email.message import EmailMessage
 
# Create message
msg = EmailMessage()
msg['Subject'] = 'Hello from Python'
msg['From'] = 'sender@example.com'
msg['To'] = 'recipient@example.com'
msg.set_content('This is a test email.')
 
# Send via SMTP
with smtplib.SMTP('smtp.example.com', 587) as server:
    server.starttls()
    server.login('username', 'password')
    server.send_message(msg)

Using Gmail

import smtplib
from email.message import EmailMessage
 
msg = EmailMessage()
msg['Subject'] = 'Test from Gmail'
msg['From'] = 'you@gmail.com'
msg['To'] = 'recipient@example.com'
msg.set_content('Sent via Python!')
 
with smtplib.SMTP_SSL('smtp.gmail.com', 465) as server:
    server.login('you@gmail.com', 'app-password')
    server.send_message(msg)

Note: Gmail requires an "App Password" (not your main password) when 2FA is enabled.

HTML Email

from email.message import EmailMessage
 
msg = EmailMessage()
msg['Subject'] = 'HTML Email'
msg['From'] = 'sender@example.com'
msg['To'] = 'recipient@example.com'
 
# Plain text fallback
msg.set_content('Your email client does not support HTML.')
 
# HTML version
msg.add_alternative("""
<html>
  <body>
    <h1>Hello!</h1>
    <p>This is an <b>HTML</b> email.</p>
  </body>
</html>
""", subtype='html')

Attachments

from email.message import EmailMessage
import mimetypes
 
msg = EmailMessage()
msg['Subject'] = 'Document attached'
msg['From'] = 'sender@example.com'
msg['To'] = 'recipient@example.com'
msg.set_content('Please see the attached file.')
 
# Attach a file
filename = 'report.pdf'
with open(filename, 'rb') as f:
    file_data = f.read()
    mime_type, _ = mimetypes.guess_type(filename)
    maintype, subtype = mime_type.split('/')
    msg.add_attachment(
        file_data,
        maintype=maintype,
        subtype=subtype,
        filename=filename
    )

Multiple Recipients

from email.message import EmailMessage
 
msg = EmailMessage()
msg['Subject'] = 'Team Update'
msg['From'] = 'sender@example.com'
msg['To'] = 'alice@example.com, bob@example.com'
msg['Cc'] = 'manager@example.com'
msg['Bcc'] = 'archive@example.com'
msg.set_content('Meeting at 3pm.')

SMTP Connection Options

import smtplib
 
# Plain SMTP (port 25) - rarely used, often blocked
with smtplib.SMTP('mail.example.com', 25) as server:
    server.send_message(msg)
 
# STARTTLS (port 587) - starts plain, upgrades to TLS
with smtplib.SMTP('mail.example.com', 587) as server:
    server.starttls()
    server.login('user', 'pass')
    server.send_message(msg)
 
# SSL/TLS (port 465) - encrypted from start
with smtplib.SMTP_SSL('mail.example.com', 465) as server:
    server.login('user', 'pass')
    server.send_message(msg)

Error Handling

import smtplib
 
try:
    with smtplib.SMTP('smtp.example.com', 587) as server:
        server.starttls()
        server.login('user', 'pass')
        server.send_message(msg)
except smtplib.SMTPAuthenticationError:
    print("Login failed")
except smtplib.SMTPConnectError:
    print("Could not connect to server")
except smtplib.SMTPRecipientsRefused:
    print("All recipients refused")
except smtplib.SMTPException as e:
    print(f"SMTP error: {e}")

Debug Mode

import smtplib
 
with smtplib.SMTP('smtp.example.com', 587) as server:
    server.set_debuglevel(1)  # Print SMTP conversation
    server.starttls()
    server.login('user', 'pass')
    server.send_message(msg)

Email Builder Function

import smtplib
from email.message import EmailMessage
import mimetypes
from pathlib import Path
 
def send_email(
    to: str | list,
    subject: str,
    body: str,
    html: str = None,
    attachments: list = None,
    smtp_host: str = 'smtp.example.com',
    smtp_port: int = 587,
    username: str = None,
    password: str = None,
):
    msg = EmailMessage()
    msg['Subject'] = subject
    msg['From'] = username
    msg['To'] = to if isinstance(to, str) else ', '.join(to)
    
    msg.set_content(body)
    
    if html:
        msg.add_alternative(html, subtype='html')
    
    if attachments:
        for filepath in attachments:
            path = Path(filepath)
            mime_type, _ = mimetypes.guess_type(str(path))
            mime_type = mime_type or 'application/octet-stream'
            maintype, subtype = mime_type.split('/')
            
            msg.add_attachment(
                path.read_bytes(),
                maintype=maintype,
                subtype=subtype,
                filename=path.name
            )
    
    with smtplib.SMTP(smtp_host, smtp_port) as server:
        server.starttls()
        server.login(username, password)
        server.send_message(msg)
 
# Usage
send_email(
    to=['alice@example.com', 'bob@example.com'],
    subject='Weekly Report',
    body='Please see attached.',
    attachments=['report.pdf', 'data.csv'],
    username='me@example.com',
    password='secret'
)

Local Testing

Use Python's built-in debug server:

# Terminal 1: Start debug server
python -m smtpd -c DebuggingServer -n localhost:1025
# Terminal 2: Send to debug server
with smtplib.SMTP('localhost', 1025) as server:
    server.send_message(msg)

Environment Variables for Credentials

import os
import smtplib
 
SMTP_HOST = os.environ.get('SMTP_HOST', 'smtp.example.com')
SMTP_USER = os.environ['SMTP_USER']
SMTP_PASS = os.environ['SMTP_PASS']
 
with smtplib.SMTP(SMTP_HOST, 587) as server:
    server.starttls()
    server.login(SMTP_USER, SMTP_PASS)
    server.send_message(msg)

Summary

smtplib handles the SMTP protocol:

  • SMTP() for plain/STARTTLS connections
  • SMTP_SSL() for SSL connections
  • starttls() to upgrade to encrypted
  • login() for authentication
  • send_message() to send

Use the email module (EmailMessage) to construct messages with HTML and attachments. Always use TLS in production.

React to this post: