Python's standard library includes smtplib for the SMTP connection and email.message.EmailMessage for building the message, so a basic SMTP send requires no third-party packages. For production delivery, an HTTP email API manages IP warmup, retries, and bounce events without requiring you to run a mail server.
How do you send email with the standard library?
EmailMessage builds the message and sets headers for you. smtplib.SMTP_SSL opens an encrypted connection on port 465:
import smtplib
from email.message import EmailMessage
msg = EmailMessage()
msg["From"] = "you@yourdomain.com"
msg["To"] = "recipient@example.org"
msg["Subject"] = "Your receipt"
msg.set_content("Thanks for your order. This is the plain-text version.")
msg.add_alternative("<p>Thanks for your order.</p>", subtype="html")
with smtplib.SMTP_SSL("smtp.example.com", 465) as server:
server.login("smtp-user", "smtp-password")
server.send_message(msg)
set_content writes the plain-text body and add_alternative attaches the HTML version, so clients pick what they can render. Sending both parts is good practice and helps with deliverability.
SSL or STARTTLS?
Two encryption paths exist and they use different ports. SMTP_SSL on 465 negotiates TLS from the first byte. The alternative is plain SMTP on 587 followed by starttls(), which upgrades a cleartext connection:
with smtplib.SMTP("smtp.example.com", 587) as server:
server.starttls()
server.login("smtp-user", "smtp-password")
server.send_message(msg)
Pick one based on what your mail server supports. Do not send credentials over an unencrypted connection. Keep the username and password in environment variables, read with os.environ, rather than in source code.
How do you add an attachment?
add_attachment takes the bytes plus a MIME type. Read the file in binary mode:
with open("invoice.pdf", "rb") as f:
msg.add_attachment(
f.read(),
maintype="application",
subtype="pdf",
filename="invoice-1042.pdf",
)
Why move off raw SMTP for production?
Production sending adds requirements the standard library does not handle. You need a mail server with a clean IP reputation, SPF, DKIM, and DMARC records, retry logic for transient failures, and a way to track bounces and complaints. An HTTP API handles that layer: you authenticate with an API key, send JSON, and read structured responses instead of parsing SMTP status codes.
Send it with Bird
Install the Bird Python SDK (pip install messagebird-sdk) to send over HTTPS without managing an SMTP connection:
from bird import APIError, Bird
client = Bird()
try:
message = client.email.send(
from_="onboarding@messagebird.dev",
to=["delivered@messagebird.dev"],
subject="Hello from Python",
text="It works.",
html="<p>It works.</p>",
)
print(message.id, message.status)
except APIError as err:
print("send failed:", err)
Bird() reads BIRD_API_KEY from the environment automatically. Sending from onboarding@messagebird.dev to the delivered@messagebird.dev sandbox address verifies your key and payload before you configure a custom domain. If the request fails validation, APIError surfaces the HTTP status code and error code.