Send email in Python: smtplib or an email API?
Updated
Python gives you two common ways to send email: speak SMTP yourself with the standard library, or call an HTTP API. Both work. They fail in different places.
Option 1: smtplib
import os
import smtplib
from email.message import EmailMessage
msg = EmailMessage()
msg["From"] = "hello@yourdomain.com"
msg["To"] = "ada@example.com"
msg["Subject"] = "Welcome"
msg.set_content("Hi Ada, your account is ready.")
msg.add_alternative("<p>Hi Ada, your account is ready.</p>", subtype="html")
with smtplib.SMTP(os.environ["SMTP_HOST"], 587, timeout=10) as smtp:
smtp.starttls()
smtp.login(os.environ["SMTP_USER"], os.environ["SMTP_PASSWORD"])
smtp.send_message(msg)
This works against any SMTP server. Port 587 with starttls() is the standard submission setup, and port 465 uses implicit TLS through smtplib.SMTP_SSL.
What you take on:
- Credentials for an SMTP account, stored and rotated by you
- Outbound port access (many hosts restrict 25 and sometimes 465 or 587)
- A new connection and TLS handshake per message, unless you pool
- SMTP reply codes as the only error detail, and bounces that arrive later by email
- Templates and merge data, which you render yourself
Option 2: an email API
pip install inboxili
import os
from inboxili import Inboxili, InboxiliError
client = Inboxili(os.environ["INBOXILI_API_KEY"])
try:
result = client.emails.send(
to="ada@example.com",
from_email="hello@yourdomain.com", # a sender on a verified domain
subject="Welcome, {{first_name}}",
html_body="<p>Hi {{first_name}}, your account is ready.</p>",
text_body="Hi Ada, your account is ready.",
template_data={"first_name": "Ada"},
)
print(result.message_id)
except InboxiliError as e:
print(e.status, e.code, e.message)
This is one HTTPS request on port 443, authenticated by an API key you can scope, restrict by IP and revoke. Failures come back as a status and a code (sender_not_verified, rate_limited), and delivery outcomes arrive as webhooks.
Where each one hurts
| | smtplib | API | |---|---|---| | Blocked network | Common on cloud hosts | Rare, it is HTTPS | | Error handling | Parse SMTP codes | Structured codes | | Attachments, CC, BCC | Built in | Not in the Inboxili API | | Works with any provider | Yes | Only that provider | | Delivery status | Not available | Webhooks and logs | | Key scoping | Usually one password | Per-key scopes and expiry |
The Inboxili API sends one recipient per request, with no attachments, CC, BCC or reply-to. If your Python code relies on those, SMTP to a provider that supports them is the better fit.
How to choose
- A one-off script that emails you: smtplib is fine.
- A web app sending verification and reset emails: use an API. You will want the error detail and delivery events.
- Anything needing attachments or CC: use SMTP with a provider that supports them.
- A Django or Celery job: use the API from a task, and see the Django guide.
One mistake in both
Whichever you choose, do not retry blindly after a timeout. Neither SMTP nor this API tells you whether a timed-out message was sent. Record that you sent it before you try again, as explained in the idempotency article.
Frequently asked questions
- Is smtplib good enough for production?
- It is a fine SMTP client, but it leaves connection handling, authentication setup, retries and sender reputation to you and your SMTP provider. For application email, an API is usually simpler to operate.
- Does Inboxili support SMTP from Python?
- No. Inboxili is API-only, so use the inboxili package or plain HTTP.
Send from Python with Inboxili
Create a workspace, verify a domain, and make your first API call.