Email/

Sending Emails With Python

Python can send email with nothing installed. The standard library ships smtplib for the SMTP connection and email.message.EmailMessage for building the message, so a working script is about fifteen lines. Below is that script with TLS done correctly, then the case for moving to an HTTP API once real traffic shows up.

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, not hard-coded.

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?

The script works, but production adds problems the standard library does not solve. You need a mail server with a clean IP reputation, correct SPF, DKIM, and DMARC records, retry logic for transient failures, and a way to see bounces and complaints. An HTTP API folds all of that in. You authenticate with a key, send JSON, and read structured responses instead of parsing SMTP status codes.

Send it with Bird

The Bird Python SDK sends the same message without the SMTP plumbing:

import bird

client = bird.Bird(api_key=os.environ["BIRD_API_KEY"])

data, error = client.emails.send(
    from_="you@yourdomain.com",
    to="delivered@bird.dev",
    subject="Hello from Python",
    html="<p>It works.</p>",
)

The delivered@bird.dev sandbox address always accepts mail, so the first run confirms your key and payload before you point at real recipients. The SDK returns a (data, error) pair, which keeps error handling explicit rather than catching SMTP exceptions. If PHP is more your language, the PHPMailer walkthrough covers the equivalent in that ecosystem.

FAQ

Do I need to install anything to send email in Python?

No. smtplib and email.message are in the standard library, so a basic SMTP send needs no packages. You only add a dependency when you want a higher-level client or an HTTP API.

What is the difference between port 465 and 587?

Port 465 expects TLS from the start, which smtplib.SMTP_SSL handles. Port 587 starts in cleartext and upgrades with starttls(). Both end up encrypted; they just negotiate it at different points.

When should I switch from smtplib to an API?

When you care about deliverability, retries, and event tracking. The standard library sends a message; it does not manage reputation or report what happened after the handoff. An API like Bird email covers that layer.

For the full request and response shape, the sending email guide walks through the API end to end.

Zacznij od jednego kanału.
Dodaj kolejne, gdy będziesz gotowy.

Testowy klucz API otrzymasz od razu. Dostęp produkcyjny odblokujesz po dodaniu metody płatności i weryfikacji nadawcy.

Używasz Claude Code, Cursor lub Codex? Skopiuj prompt konfiguracyjny, a Twój agent zainstaluje za Ciebie Bird CLI i umiejętności. Wybierz swój:

Cursor