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.
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.
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:
| Provider | Host | Port | Auth |
|---|---|---|---|
| SendPigeon | smtp.sendpigeon.dev | 587 | API key as user + password |
| Gmail | smtp.gmail.com | 587 | App Password (not regular password) |
| AWS SES | email-smtp.{region}.amazonaws.com | 587 | SES SMTP credentials |
| Outlook | smtp.office365.com | 587 | Email + password |
| SendGrid | smtp.sendgrid.net | 587 | apikey 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 API | smtplib | |
|---|---|---|
| Setup | pip install sendpigeon + API key | No install, but verbose MIME code |
| Connection | Stateless HTTP request | Persistent SMTP connection |
| Error handling | Structured response with status codes | String-matching SMTP error messages |
| Bounce handling | Automatic suppression | Build it yourself |
| Tracking | Opens, clicks, delivery status | None |
| Serverless | Works everywhere (HTTP) | May fail (SMTP ports blocked) |
| Retries | Built-in with backoff | Build 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
- Set up DKIM, SPF, and DMARC on your sending domain
- Review the email deliverability checklist
- Learn how to send queued email for background processing
- Test locally with the SendPigeon CLI
- Browse email templates for ready-to-use HTML
- See the Node.js email guide if you also work in JavaScript