Back to blog
PythonEmailTutorialFlaskDjangoFastAPI

How to Send Email in Python (SMTP + API)

Send email from Python using smtplib or an email API. Complete guide with code examples for Flask, Django, FastAPI, plain scripts, attachments, HTML email, and local testing.

SendPigeon TeamMarch 27, 20267 min read

To send email in Python, use an email API (one HTTP call) or the built-in smtplib module (SMTP protocol). The API approach is simpler — no MIME message building, no SMTP connection management. The smtplib approach gives you lower-level control but requires more setup.

This guide covers both methods with code examples for plain Python scripts, Flask, Django, and FastAPI.

TL;DR

Email API (simplest):

from sendpigeon import SendPigeon
client = SendPigeon("sp_live_xxx")
client.send(from_="hello@yourdomain.com", to="user@example.com",
            subject="Hello", html="<h1>Hello!</h1>")

smtplib (built-in):

import smtplib
from email.mime.text import MIMEText
msg = MIMEText("<h1>Hello!</h1>", "html")
msg["Subject"], msg["From"], msg["To"] = "Hello", "you@domain.com", "user@example.com"
with smtplib.SMTP("smtp.provider.com", 587) as s:
    s.starttls(); s.login(user, password); s.send_message(msg)

Use an API for production apps. Use smtplib when you need raw SMTP control or have existing SMTP infrastructure.


Method 1: Email API (Recommended)

An email API handles SMTP connections, authentication, bounce processing, and retries for you. One HTTP call sends the email.

Install

pip install sendpigeon

Send an email

import os
from sendpigeon import SendPigeon

client = SendPigeon(os.environ["SENDPIGEON_API_KEY"])

result = client.send(
    from_="hello@yourdomain.com",
    to="user@example.com",
    subject="Your order shipped",
    html="<h1>Your order has shipped</h1><p>Track it here.</p>",
)

if result.error:
    print(f"Failed: {result.error.message}")
else:
    print(f"Sent: {result.data.id}")

That's it. No SMTP configuration, no MIME message building, no connection management.

Send with attachments

import base64

with open("invoice.pdf", "rb") as f:
    pdf_content = base64.b64encode(f.read()).decode()

result = client.send(
    from_="billing@yourdomain.com",
    to="customer@example.com",
    subject="Your invoice",
    html="<p>Invoice attached.</p>",
    attachments=[
        {"filename": "invoice.pdf", "content": pdf_content}
    ],
)

Method 2: smtplib (Built-in)

Python's smtplib module sends email over SMTP. No pip install needed — it's in the standard library.

Send a plain text email

import smtplib
import os
from email.mime.text import MIMEText

msg = MIMEText("Your order has shipped. Track it here.")
msg["Subject"] = "Your order shipped"
msg["From"] = "hello@yourdomain.com"
msg["To"] = "user@example.com"

with smtplib.SMTP("smtp.yourprovider.com", 587) as server:
    server.starttls()
    server.login(os.environ["SMTP_USER"], os.environ["SMTP_PASS"])
    server.send_message(msg)

Send an HTML email

from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

msg = MIMEMultipart("alternative")
msg["Subject"] = "Your order shipped"
msg["From"] = "hello@yourdomain.com"
msg["To"] = "user@example.com"

# Plain text fallback
text = "Your order has shipped. Track it here."
html = "<h1>Your order has shipped</h1><p>Track it here.</p>"

msg.attach(MIMEText(text, "plain"))
msg.attach(MIMEText(html, "html"))

with smtplib.SMTP("smtp.yourprovider.com", 587) as server:
    server.starttls()
    server.login(os.environ["SMTP_USER"], os.environ["SMTP_PASS"])
    server.send_message(msg)

Always include a plain text alternative with HTML emails. Spam filters check for it, and some email clients prefer plain text.

Send with attachments

from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders

msg = MIMEMultipart()
msg["Subject"] = "Your invoice"
msg["From"] = "billing@yourdomain.com"
msg["To"] = "customer@example.com"

msg.attach(MIMEText("<p>Invoice attached.</p>", "html"))

# Attach a PDF
with open("invoice.pdf", "rb") as f:
    attachment = MIMEBase("application", "octet-stream")
    attachment.set_payload(f.read())
    encoders.encode_base64(attachment)
    attachment.add_header("Content-Disposition", "attachment", filename="invoice.pdf")
    msg.attach(attachment)

with smtplib.SMTP("smtp.yourprovider.com", 587) as server:
    server.starttls()
    server.login(os.environ["SMTP_USER"], os.environ["SMTP_PASS"])
    server.send_message(msg)

The MIME attachment API is verbose. This is one of the main reasons developers switch to an email API — compare this with the API version above.


SMTP Provider Configuration

Common SMTP settings for smtplib:

ProviderHostPortAuth
SendPigeonsmtp.sendpigeon.dev587API key as user + password
Gmailsmtp.gmail.com587App Password (not regular password)
AWS SESemail-smtp.{region}.amazonaws.com587SES SMTP credentials
Outlooksmtp.office365.com587Email + password
SendGridsmtp.sendgrid.net587apikey as user, API key as password

Gmail limits you to ~500 emails/day and requires an App Password. It's fine for testing but not for production email.


Send Email from Flask

Flask doesn't include email support. Use the SendPigeon SDK or smtplib directly.

With an email API

from flask import Flask, request, jsonify
from sendpigeon import SendPigeon
import os

app = Flask(__name__)
client = SendPigeon(os.environ["SENDPIGEON_API_KEY"])

@app.route("/api/contact", methods=["POST"])
def contact():
    data = request.json
    result = client.send(
        from_="contact@yourdomain.com",
        to="you@yourdomain.com",
        subject=f"Contact form: {data['name']}",
        html=f"<p><strong>From:</strong> {data['name']} ({data['email']})</p>"
             f"<p>{data['message']}</p>",
        reply_to=data["email"],
    )

    if result.error:
        return jsonify({"error": "Failed to send"}), 500
    return jsonify({"success": True})

Send Email from Django

Django has built-in email support via django.core.mail.

Configure SMTP in settings.py

# settings.py
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "smtp.sendpigeon.dev"
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = os.environ["SENDPIGEON_API_KEY"]
EMAIL_HOST_PASSWORD = os.environ["SENDPIGEON_API_KEY"]
DEFAULT_FROM_EMAIL = "hello@yourdomain.com"

Send from a view

from django.core.mail import send_mail

def signup_view(request):
    # ... create user ...

    send_mail(
        subject="Welcome!",
        message="Thanks for signing up.",  # Plain text
        html_message="<h1>Welcome!</h1><p>Thanks for signing up.</p>",
        from_email="hello@yourdomain.com",
        recipient_list=[user.email],
    )

    return JsonResponse({"success": True})

Or use the SDK directly

from sendpigeon import SendPigeon
import os

client = SendPigeon(os.environ["SENDPIGEON_API_KEY"])

def signup_view(request):
    # ... create user ...

    result = client.send(
        from_="hello@yourdomain.com",
        to=user.email,
        subject="Welcome!",
        html="<h1>Welcome!</h1><p>Thanks for signing up.</p>",
    )

    return JsonResponse({"success": True})

The SDK gives you delivery status, error details, and webhook events that Django's built-in send_mail doesn't provide.


Send Email from FastAPI

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from sendpigeon import SendPigeon
import os

app = FastAPI()
client = SendPigeon(os.environ["SENDPIGEON_API_KEY"])

class ContactForm(BaseModel):
    name: str
    email: str
    message: str

@app.post("/api/contact")
def contact(form: ContactForm):
    result = client.send(
        from_="contact@yourdomain.com",
        to="you@yourdomain.com",
        subject=f"Contact form: {form.name}",
        html=f"<p><strong>From:</strong> {form.name} ({form.email})</p>"
             f"<p>{form.message}</p>",
        reply_to=form.email,
    )

    if result.error:
        raise HTTPException(status_code=500, detail="Failed to send")
    return {"success": True}

API vs smtplib: When to Use Each

Email APIsmtplib
Setuppip install sendpigeon + API keyNo install, but verbose MIME code
ConnectionStateless HTTP requestPersistent SMTP connection
Error handlingStructured response with status codesString-matching SMTP error messages
Bounce handlingAutomatic suppressionBuild it yourself
TrackingOpens, clicks, delivery statusNone
ServerlessWorks everywhere (HTTP)May fail (SMTP ports blocked)
RetriesBuilt-in with backoffBuild it yourself

Use an API for production web apps, serverless functions, and transactional email.

Use smtplib when you need raw SMTP control, are sending between internal systems, or already have SMTP infrastructure.


Test Locally

Catch emails locally without sending to real inboxes:

npx @sendpigeon-sdk/cli dev

This starts a local SMTP server. Point your smtplib config at it:

with smtplib.SMTP("localhost", 4125) as server:
    server.send_message(msg)  # Caught locally

View captured emails at localhost:4100. See the local email testing guide for full setup.


FAQ

What is the easiest way to send email in Python?

Use an email API. Install with pip install sendpigeon, create a client with your API key, and call client.send(). One function call — no SMTP configuration, no MIME message building, no connection management.

How do I send email with smtplib?

Import smtplib and email.mime modules, build a MIMEMultipart or MIMEText message, connect with smtplib.SMTP(), call starttls() and login(), then send_message(). See the code examples above.

Can I send HTML email in Python?

Yes. With smtplib, create a MIMEText with subtype "html" and attach it to a MIMEMultipart("alternative") message. With an email API, pass the HTML string directly to the html parameter.

How do I send email from Django?

Django has built-in send_mail() in django.core.mail. Configure SMTP settings in settings.py and call send_mail() from your views. Or use an SDK directly for more control over delivery tracking and error handling.

How do I send email from Flask?

Flask doesn't include email support. Use the SendPigeon SDK (pip install sendpigeon) or Python's built-in smtplib directly in your route handlers.

How do I send email from FastAPI?

Use an email API SDK in your endpoint handlers. The SendPigeon SDK works in FastAPI's sync and async contexts. For SMTP, run smtplib calls in a thread to avoid blocking the event loop.

Why is my Python email going to spam?

Most likely missing authentication. Set up SPF, DKIM, and DMARC on your sending domain. Also check that you're not using a free email domain (@gmail.com) as your from address — these have strict DMARC policies that block third-party sending.


Next Steps