Send Emails with Flask
Learn how to send transactional emails using PostStack and Flask.
Flask-Mail wraps SMTP, which is fine for contact forms but leaves deliverability, webhooks, and suppression handling to you. For transactional product email, call the PostStack API directly. The idiomatic Flask pattern is to create one `PostStack` client in your app factory, stash it on `app.extensions`, and reach it from views via `current_app` — then offload the actual send to a background worker (RQ or Celery) so request handlers never wait on the network.
1. Install the SDK
pip install poststack2. Initialize the client
# app.py — initialise the client once in your app factory
import os
from flask import Flask
from poststack import PostStack
def create_app():
app = Flask(__name__)
# Store the client on app.extensions so views can reach it via current_app
app.extensions["poststack"] = PostStack(api_key=os.environ["POSTSTACK_API_KEY"])
return app3. Send an email
# queue.py — one Redis connection + Queue, imported by both web and worker
from redis import Redis
from rq import Queue
queue = Queue(connection=Redis.from_url("redis://localhost:6379"))
# views.py (a blueprint)
from flask import Blueprint, jsonify, request
from .queue import queue
from .tasks import send_welcome_email
bp = Blueprint("auth", __name__)
@bp.post("/signup")
def signup():
user = create_user(request.form)
queue.enqueue(send_welcome_email, user.email, user.name) # response stays fast
return jsonify(ok=True)
# tasks.py — runs in an RQ worker process, which has NO Flask app context,
# so use a module logger here rather than current_app.
import os
import logging
from poststack import PostStack, PostStackError
logger = logging.getLogger(__name__)
def send_welcome_email(email: str, name: str):
ps = PostStack(api_key=os.environ["POSTSTACK_API_KEY"])
try:
return ps.emails.send({
"from": "hello@yourdomain.com",
"to": [email],
"subject": "Welcome to Acme",
"html": f"<h1>Welcome, {name}!</h1>",
})
except PostStackError as exc:
logger.error("PostStack send failed: %s (req %s)", exc.error, exc.request_id)
raise4. Handle errors
Flask idioms for error handling, retries, and structured logging when calling the PostStack API.
import os, logging
from poststack import PostStack, PostStackError
logger = logging.getLogger(__name__)
ps = PostStack(api_key=os.environ["POSTSTACK_API_KEY"])
def send(payload):
# The SDK retries 429/5xx internally; handle only terminal errors here.
try:
return ps.emails.send(payload)
except PostStackError as e:
if 400 <= e.status_code < 500 and e.status_code != 429:
logger.error("PostStack %s: %s (req %s)", e.code, e.error, e.request_id)
return None
# 429/5xx survived the SDK's own retries — surface it to the worker
raiseFramework integrations
App factory + extension pattern
Create the client once inside `create_app()` and store it on `app.extensions["poststack"]`. Views call `current_app.extensions["poststack"].emails.send(...)`. This avoids constructing a new client (and a new connection pool) on every request.
RQ / Celery background jobs
Enqueue the send rather than calling it inline. In the worker, construct the client from `os.environ` and add retry-with-backoff around `PostStackError`. The worker process is the right place for the network call, not the request thread.
Blueprints
Keep email-sending helpers in a dedicated module imported by your blueprints, so the same `send_welcome_email` helper is reusable across `auth`, `billing`, and `account` blueprints.
Flask-Mail vs the SDK
Use Flask-Mail only if you specifically need SMTP for a legacy reason. The SDK gives you the REST API directly — webhooks, tracking, idempotency, and per-message IDs that SMTP cannot return.
Common pitfalls
Constructing a client per request
Creating `PostStack(...)` inside every view spins up a fresh HTTP connection pool each time. Build it once in the app factory and reuse it.
Sending inline in the request handler
A blocking `emails.send()` in the view adds API latency to every response. Enqueue to RQ/Celery and return immediately.
Reading the key outside app/worker context
Load `POSTSTACK_API_KEY` from the environment in both the web app and the worker — they are separate processes and each needs the secret injected.
Notes
- Initialise one `PostStack` client in the app factory; reuse it via `current_app.extensions`
- Push sends to RQ or Celery so request handlers stay fast
FAQ
Where should I create the PostStack client in a Flask app?
In your app factory, once. Store it on `app.extensions["poststack"]` and access it through `current_app` in views. This reuses one HTTP connection pool instead of building a new client per request.
How do I send email in the background with Flask?
Enqueue the send with RQ or Celery from your view, and do the actual `ps.emails.send(...)` in the worker with retry-on-`PostStackError`. The request handler returns immediately.
Should I use Flask-Mail or the PostStack SDK?
Use the SDK for transactional product email — it gives you the REST API, webhooks, tracking, and per-message IDs. Flask-Mail only makes sense if you are tied to SMTP for a specific reason.
Related guides
Ready to send emails with Flask?
Create a free account and get your API key in under a minute.