Flask Integration
This guide shows how to integrate genro-mail-proxy with a Flask application.
Flask has no built-in email system. The common solution is Flask-Mail, which is synchronous and blocks the request thread during SMTP delivery. For async delivery, you typically need Celery + Redis, which adds operational complexity.
genro-mail-proxy provides an alternative: a dedicated email service that handles queuing, retry, and delivery reports. It acts as a specialized message broker for email, with built-in SMTP handling and delivery tracking.
Note
The HTTP calls to enqueue messages are synchronous (blocking) in the examples below. The “async” benefit is that SMTP delivery happens in the background after the HTTP call returns. For truly non-blocking enqueue, use an async HTTP client or a background thread.
When to use the proxy with Flask
Consider genro-mail-proxy when:
You don’t want to introduce Celery just for email
You need delivery reports with automatic callback
Multiple services share the same email infrastructure
You need rate limiting shared across Flask instances
When Flask-Mail is sufficient:
Low email volume where blocking is acceptable
Simple transactional emails without retry requirements
Single Flask instance with no rate limiting needs
Comparison with Flask-Mail + Celery
Feature |
Flask-Mail + Celery |
genro-mail-proxy |
|---|---|---|
Dependencies |
Redis/RabbitMQ + Celery worker |
Only the proxy |
Delivery reports |
Manual implementation |
Built-in HTTP callback |
Retry on failure |
Celery retry decorator |
Built-in exponential backoff |
Rate limiting |
Manual implementation |
Built-in per account |
Blocking HTTP call |
No (with Celery) |
Yes (enqueue is sync)* |
Operational complexity |
High (broker + worker) |
Medium (one service) |
* The HTTP call to enqueue messages blocks until the proxy accepts them, but SMTP delivery happens asynchronously. For non-blocking enqueue, use an async HTTP client (httpx with asyncio) or a background thread.
Installation
pip install requests # or httpx
Configuration
# config.py
class Config:
MAIL_PROXY_URL = "http://localhost:8000"
MAIL_PROXY_TOKEN = "your-api-token"
MAIL_PROXY_ACCOUNT = "default"
Client module
# mail_proxy.py
"""Flask client for genro-mail-proxy."""
import uuid
import requests
from flask import current_app
class MailProxy:
"""Client for sending emails through genro-mail-proxy."""
def _config(self, key):
return current_app.config.get(key)
def _headers(self):
return {
"X-API-Token": self._config("MAIL_PROXY_TOKEN"),
"Content-Type": "application/json",
}
def send_mail(
self,
subject,
body,
sender,
recipients,
cc=None,
bcc=None,
html=None,
attachments=None,
priority=2,
message_id=None,
):
"""Send an email through the mail proxy.
Args:
subject: Email subject.
body: Plain text body.
sender: Sender email address.
recipients: List of recipient addresses.
cc: List of CC addresses (optional).
bcc: List of BCC addresses (optional).
html: HTML body (optional).
attachments: List of attachment dicts (optional).
priority: 0=immediate, 1=high, 2=medium (default), 3=low.
message_id: Custom message ID (auto-generated if not provided).
Returns:
Dict with "queued" count and "rejected" list.
"""
message = {
"id": message_id or str(uuid.uuid4()),
"account_id": self._config("MAIL_PROXY_ACCOUNT") or "default",
"from": sender,
"to": recipients if isinstance(recipients, list) else [recipients],
"subject": subject,
"body": html or body,
"content_type": "html" if html else "plain",
"priority": priority,
}
if cc:
message["cc"] = cc if isinstance(cc, list) else [cc]
if bcc:
message["bcc"] = bcc if isinstance(bcc, list) else [bcc]
if attachments:
message["attachments"] = attachments
response = requests.post(
f"{self._config('MAIL_PROXY_URL')}/commands/add-messages",
headers=self._headers(),
json={"messages": [message]},
timeout=10,
)
response.raise_for_status()
return response.json()
# Module-level instance
mail_proxy = MailProxy()
Flask application
# app.py
from flask import Flask, jsonify, request
from mail_proxy import mail_proxy
app = Flask(__name__)
app.config.from_object("config.Config")
@app.route("/send-welcome/<int:user_id>", methods=["POST"])
def send_welcome(user_id):
# Get user from database
user = get_user(user_id)
result = mail_proxy.send_mail(
subject=f"Welcome {user.name}!",
body=f"Hello {user.name}, thanks for signing up.",
sender="noreply@example.com",
recipients=[user.email],
priority=1,
)
return jsonify(result)
@app.route("/send-invoice/<int:invoice_id>", methods=["POST"])
def send_invoice(invoice_id):
invoice = get_invoice(invoice_id)
result = mail_proxy.send_mail(
subject=f"Invoice #{invoice.number}",
body=f"Please find attached invoice #{invoice.number}.",
html=f"<p>Please find attached invoice <strong>#{invoice.number}</strong>.</p>",
sender="billing@example.com",
recipients=[invoice.customer_email],
attachments=[
{
"filename": f"invoice_{invoice.number}.pdf",
"storage_path": f"invoice_id={invoice.id}",
"fetch_mode": "endpoint",
}
],
)
return jsonify(result)
Delivery reports endpoint
Important: Authenticate the request to ensure it comes from your mail proxy
instance. The proxy sends the token configured in the tenant’s auth_token field.
# app.py (continued)
from functools import wraps
def verify_proxy_auth(f):
"""Decorator to verify mail proxy authentication."""
@wraps(f)
def decorated(*args, **kwargs):
# Check Bearer token (configured in tenant's auth_token)
auth_header = request.headers.get("Authorization", "")
expected_token = current_app.config.get("MAIL_PROXY_SYNC_TOKEN")
if expected_token:
if not auth_header.startswith("Bearer "):
return jsonify({"error": "Missing authorization"}), 403
token = auth_header[7:] # Remove "Bearer " prefix
if token != expected_token:
return jsonify({"error": "Invalid token"}), 403
return f(*args, **kwargs)
return decorated
@app.route("/mail/delivery-report", methods=["POST"])
@verify_proxy_auth
def delivery_report():
"""Receive delivery reports from the mail proxy."""
data = request.get_json()
sent = 0
error = 0
for report in data.get("delivery_report", []):
message_id = report["id"]
if report.get("sent_ts"):
sent += 1
# Update your database
# mark_email_sent(message_id)
elif report.get("error_ts"):
error += 1
# Log the error
# mark_email_failed(message_id, report.get("error"))
return jsonify({"ok": True, "queued": 0})
@app.route("/mail/attachments", methods=["POST"])
def serve_attachment():
"""Serve attachment content to the mail proxy."""
invoice_id = request.form.get("invoice_id")
if invoice_id:
invoice = get_invoice(invoice_id)
pdf_content = generate_invoice_pdf(invoice)
return pdf_content, 200, {"Content-Type": "application/pdf"}
return "", 404
Flask-Mail comparison
For reference, here’s how Flask-Mail with Celery looks:
# With Flask-Mail + Celery
from flask_mail import Mail, Message
from celery import Celery
mail = Mail(app)
celery = Celery(app.name, broker="redis://localhost:6379/0")
@celery.task
def send_async_email(subject, sender, recipients, body):
with app.app_context():
msg = Message(subject, sender=sender, recipients=recipients)
msg.body = body
mail.send(msg)
# No delivery report - you don't know if it succeeded
# Usage
send_async_email.delay("Welcome!", "noreply@example.com", [user.email], "Hello")
The Celery approach requires running a Redis server and a Celery worker process. The mail proxy consolidates this into a single service with built-in delivery tracking.
Blueprint example
For larger applications, organize the mail functionality as a blueprint:
# blueprints/mail.py
from flask import Blueprint, jsonify, request
from mail_proxy import mail_proxy
mail_bp = Blueprint("mail", __name__, url_prefix="/mail")
@mail_bp.route("/send", methods=["POST"])
def send():
data = request.get_json()
result = mail_proxy.send_mail(
subject=data["subject"],
body=data.get("body", ""),
html=data.get("html"),
sender=data["from"],
recipients=data["to"],
priority=data.get("priority", 2),
)
return jsonify(result)
@mail_bp.route("/delivery-report", methods=["POST"])
def delivery_report():
data = request.get_json()
# Process reports...
return jsonify({"ok": True, "queued": 0})
# app.py
from blueprints.mail import mail_bp
app.register_blueprint(mail_bp)
Proxy tenant configuration
Configure the proxy tenant to point to your Flask endpoints:
mail-proxy myserver tenants add myflaskapp \
--base-url "https://myflaskapp.example.com" \
--sync-path "/mail/delivery-report" \
--attachment-path "/mail/attachments" \
--auth-method bearer \
--auth-token "shared-secret"