pyguides

smtplib module

Overview

smtplib is Python’s standard library for sending email over SMTP (Simple Mail Transfer Protocol). It handles the protocol-level details — connecting to a mail server, authenticating, sending the envelope, and closing the connection. Pair it with the email module to construct properly formatted messages with headers and attachments.

Sending a Simple Email

With a local SMTP server

If you have an SMTP server running locally (common on Linux servers):

import smtplib

with smtplib.SMTP("localhost", 587) as server:
    server.sendmail(
        "alice@example.com",
        ["bob@example.com"],
        "Subject: Hello\n\nBody of the message."
    )

With an external SMTP server

For Gmail, AWS SES, Mailgun, or any external provider:

import smtplib
from email.message import EmailMessage

msg = EmailMessage()
msg["From"] = "alice@example.com"
msg["To"] = "bob@example.com"
msg["Subject"] = "Hello from smtplib"
msg.set_content("This is a plain text email sent with Python.")

with smtplib.SMTP("smtp.example.com", 587) as server:
    server.starttls()  # upgrade to TLS
    server.login("alice@example.com", "app_password")
    server.send_message(msg)

SMTP Connection Classes

smtplib provides three connection classes:

ClassDescription
SMTPPlain SMTP, unencrypted
SMTP_SSLSMTP over SSL, encrypted from the start
SMTP_TLS (context manager)Starts unencrypted, upgrades via STARTTLS
import smtplib

# Plain SMTP on port 25 (unencrypted, rarely used)
server = smtplib.SMTP("smtp.example.com", 25)

# SMTP over SSL on port 465
server = smtplib.SMTP_SSL("smtp.example.com", 465)

# SMTP with STARTTLS on port 587 (most common)
server = smtplib.SMTP("smtp.example.com", 587)
server.starttls()

Authentication

Use login() with your username and password or app password:

with smtplib.SMTP("smtp.gmail.com", 587) as server:
    server.starttls()
    server.login("your-email@gmail.com", "your-app-password")
    server.sendmail("your-email@gmail.com", ["recipient@example.com"], msg.as_string())

Never put passwords in source code. Use environment variables or a secrets manager:

import os
import smtplib

password = os.environ.get("SMTP_PASSWORD")
if not password:
    raise ValueError("SMTP_PASSWORD environment variable not set")

server.login("alice@example.com", password)

Using TLS (STARTTLS)

STARTTLS upgrades an unencrypted connection to TLS. Always call starttls() after connecting and before authenticating:

import smtplib

with smtplib.SMTP("smtp.example.com", 587) as server:
    server.ehlo()  # identify yourself to the server
    server.starttls(context=None)  # upgrade to TLS
    server.ehlo()  # re-identify over TLS
    server.login("alice@example.com", password)
    server.sendmail("alice@example.com", ["bob@example.com"], msg.as_string())

The context parameter accepts an ssl.SSLContext if you need specific TLS versions or certificate verification settings.

Constructing Messages with email

Use the email.message.EmailMessage class for properly formatted messages with headers and attachments:

import smtplib
from email.message import EmailMessage

msg = EmailMessage()
msg["From"] = "alice@example.com"
msg["To"] = "bob@example.com, carol@example.com"
msg["Cc"] = "dave@example.com"
msg["Subject"] = "Report attached"
msg.set_content("Please find the monthly report attached.")

# Add an attachment
with open("report.pdf", "rb") as f:
    msg.add_attachment(
        f.read(),
        maintype="application",
        subtype="pdf",
        filename="monthly_report.pdf"
    )

with smtplib.SMTP("smtp.example.com", 587) as server:
    server.starttls()
    server.login("alice@example.com", password)
    server.send_message(msg)

Sending HTML Email

from email.message import EmailMessage

msg = EmailMessage()
msg["From"] = "alice@example.com"
msg["To"] = "bob@example.com"
msg["Subject"] = "HTML Email"
msg.add_alternative(
    "<html><body><h1>Hello</h1><p>This is an <strong>HTML</strong> email.</p></body></html>",
    subtype="html"
)

with smtplib.SMTP("smtp.example.com", 587) as server:
    server.starttls()
    server.login("alice@example.com", password)
    server.send_message(msg)

Sending to Multiple Recipients

import smtplib
from email.message import EmailMessage

msg = EmailMessage()
msg["From"] = "alice@example.com"
msg["To"] = "bob@example.com, carol@example.com"
msg["Subject"] = "Group email"
msg.set_content("Hello everyone.")

with smtplib.SMTP("smtp.example.com", 587) as server:
    server.starttls()
    server.login("alice@example.com", password)

    # send_message handles To/Cc/Bcc automatically
    server.send_message(msg)

    # Or manually with separate recipients
    recipients = ["bob@example.com", "carol@example.com", "dave@example.com"]
    server.sendmail("alice@example.com", recipients, msg.as_string())

Error Handling

import smtplib
from email.message import EmailMessage

try:
    with smtplib.SMTP("smtp.example.com", 587, timeout=10) as server:
        server.starttls()
        server.login("alice@example.com", password)
        server.send_message(msg)
        print("Email sent successfully")
except smtplib.SMTPAuthenticationError:
    print("Authentication failed. Check your username and password.")
except smtplib.SMTPException as e:
    print(f"SMTP error: {e}")
except TimeoutError:
    print("Connection timed out. The SMTP server may be unreachable.")

Common exceptions:

  • SMTPAuthenticationError — wrong username/password or app password
  • SMTPRecipientsRefused — all recipients were rejected by the server
  • SMTPSenderRefused — the sender address was rejected
  • SMTPConnectError — couldn’t connect to the server
  • SMTPDataError — the server rejected the message data

SMTP Settings for Common Providers

Gmail

import smtplib

with smtplib.SMTP("smtp.gmail.com", 587) as server:
    server.starttls()
    server.login("your-email@gmail.com", "your-16-char-app-password")
    # Gmail requires an App Password, not your regular password

AWS SES

import smtplib

with smtplib.SMTP("email-smtp.us-east-1.amazonaws.com", 587) as server:
    server.starttls()
    server.login("AWS_ACCESS_KEY", "AWS_SECRET_KEY")

Office 365 / Outlook

import smtplib

with smtplib.SMTP("smtp.office365.com", 587) as server:
    server.starttls()
    server.login("your-email@outlook.com", "your-password")

Connection Context Manager

Always use a context manager to ensure the connection closes:

import smtplib

# Correct — connection is closed even if an error occurs
with smtplib.SMTP("smtp.example.com", 587) as server:
    server.starttls()
    server.login("alice@example.com", password)
    server.send_message(msg)

# Wrong — connection stays open if an error occurs
server = smtplib.SMTP("smtp.example.com", 587)
server.send_message(msg)  # connection never closed if this raises

Gotchas

Port 587 uses STARTTLS, port 465 uses SSL from the start. These are different protocols. Port 587 is more common for modern deployments. If you use SMTP_SSL on port 587 or plain SMTP on port 465, the connection will fail or behave unexpectedly.

Passwords aren’t enough for most providers. Gmail, Microsoft, and most cloud providers require an app password or OAuth2. Plain username/password authentication over TLS is increasingly deprecated.

send_message() is preferred over sendmail(). send_message() parses the To, Cc, and Bcc headers correctly, while sendmail() requires you to manage the envelope separately from the message headers.

SMTP has a strict line length limit. The SMTP protocol limits lines to 998 octets. Python’s email module handles this automatically when you use EmailMessage and send_message(). If you use sendmail() with a raw string, you need to ensure lines don’t exceed the limit.

The timeout parameter prevents hanging. Set a timeout when connecting to avoid blocking indefinitely on unreachable servers:

server = smtplib.SMTP("smtp.example.com", 587, timeout=30)

See Also