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.

Related