How to Send Emails in Flask (2026 Guide)

Nik@nikpolale
11 min read
Flask doesn't include email support out of the box. Flask-Mail adds SMTP-based sending, but you manage the SMTP server and deliverability yourself.
For production apps, API-based providers are simpler. One HTTP call, they handle delivery, retries, and bounce processing. This guide covers both approaches.
Flask-Mail vs API Providers
# Flask-Mail: SMTP-based, you manage the server
from flask_mail import Mail, Message
mail = Mail(app)
msg = Message("Hello", recipients=["user@example.com"])
msg.html = "<h1>Welcome</h1>"
mail.send(msg)
# API provider: one HTTP call
import requests
requests.post("https://api.sequenzy.com/v1/transactional/send",
headers={"Authorization": "Bearer sq_key"},
json={"to": "user@example.com", "subject": "Hello", "body": "<h1>Welcome</h1>"})Use Flask-Mail for internal SMTP servers. Use an API provider for everything else.
Install
Terminal
pip install sequenzy requestsTerminal
pip install resendTerminal
pip install sendgridCreate an Email Helper
email_service.py
import os
import requests
SEQUENZY_API_KEY = os.environ["SEQUENZY_API_KEY"]
def send_email(to: str, subject: str, body: str) -> dict:
response = requests.post(
"https://api.sequenzy.com/v1/transactional/send",
headers={"Authorization": f"Bearer {SEQUENZY_API_KEY}"},
json={"to": to, "subject": subject, "body": body},
)
response.raise_for_status()
return response.json()email_service.py
import resend
import os
resend.api_key = os.environ["RESEND_API_KEY"]
def send_email(to: str, subject: str, html: str) -> dict:
return resend.Emails.send({
"from": "Your App <noreply@yourdomain.com>",
"to": to,
"subject": subject,
"html": html,
})email_service.py
import os
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail
sg = SendGridAPIClient(os.environ["SENDGRID_API_KEY"])
def send_email(to: str, subject: str, html: str):
message = Mail(
from_email="noreply@yourdomain.com",
to_emails=to,
subject=subject,
html_content=html,
)
return sg.send(message)Send from a Route
app.py
from flask import Flask, request, jsonify, render_template
from email_service import send_email
app = Flask(__name__)
@app.post("/api/send-welcome")
def send_welcome():
data = request.get_json()
email = data.get("email")
name = data.get("name")
if not email or not name:
return jsonify({"error": "email and name required"}), 400
result = send_email(
to=email,
subject=f"Welcome, {name}",
body=f"<h1>Welcome, {name}</h1><p>Your account is ready.</p>",
)
return jsonify(result)
@app.post("/contact")
def contact():
email = request.form.get("email")
message = request.form.get("message")
if not email or not message:
return render_template("contact.html", error="All fields required")
send_email(
to="you@yourcompany.com",
subject=f"Contact from {email}",
body=f"<p><strong>From:</strong> {email}</p><p>{message}</p>",
)
return render_template("contact.html", success=True)app.py
from flask import Flask, request, jsonify, render_template
from email_service import send_email
app = Flask(__name__)
@app.post("/api/send-welcome")
def send_welcome():
data = request.get_json()
email = data.get("email")
name = data.get("name")
if not email or not name:
return jsonify({"error": "email and name required"}), 400
result = send_email(
to=email,
subject=f"Welcome, {name}",
html=f"<h1>Welcome, {name}</h1><p>Your account is ready.</p>",
)
return jsonify({"id": result["id"]})
@app.post("/contact")
def contact():
email = request.form.get("email")
message = request.form.get("message")
if not email or not message:
return render_template("contact.html", error="All fields required")
send_email(
to="you@yourcompany.com",
subject=f"Contact from {email}",
html=f"<p><strong>From:</strong> {email}</p><p>{message}</p>",
)
return render_template("contact.html", success=True)app.py
from flask import Flask, request, jsonify, render_template
from email_service import send_email
app = Flask(__name__)
@app.post("/api/send-welcome")
def send_welcome():
data = request.get_json()
email = data.get("email")
name = data.get("name")
if not email or not name:
return jsonify({"error": "email and name required"}), 400
send_email(
to=email,
subject=f"Welcome, {name}",
html=f"<h1>Welcome, {name}</h1><p>Your account is ready.</p>",
)
return jsonify({"sent": True})
@app.post("/contact")
def contact():
email = request.form.get("email")
message = request.form.get("message")
if not email or not message:
return render_template("contact.html", error="All fields required")
send_email(
to="you@yourcompany.com",
subject=f"Contact from {email}",
html=f"<p><strong>From:</strong> {email}</p><p>{message}</p>",
)
return render_template("contact.html", success=True)Jinja2 Email Templates
# templates/emails/welcome.html
"""
<h1>Welcome, {{ name }}</h1>
<p>Your account is ready.</p>
<a href="{{ dashboard_url }}"
style="display:inline-block;background:#f97316;color:#fff;padding:12px 24px;border-radius:6px;text-decoration:none;">
Go to Dashboard
</a>
"""
# In your route:
from flask import render_template
html = render_template("emails/welcome.html", name=name, dashboard_url="/dashboard")
send_email(to=email, subject=f"Welcome, {name}", body=html)Background Sending with Celery
from celery import Celery
from email_service import send_email
celery = Celery("tasks", broker="redis://localhost:6379")
@celery.task
def send_email_async(to: str, subject: str, body: str):
send_email(to=to, subject=subject, body=body)
# In your route - fire and forget
send_email_async.delay(
to=email,
subject="Welcome",
body="<h1>Welcome!</h1>",
)Going to Production
1. Verify Your Domain
Add SPF, DKIM, DMARC DNS records.
2. Use Environment Variables
export SEQUENZY_API_KEY=sq_your_key
export FLASK_ENV=production3. Use Gunicorn
gunicorn app:app -w 4Beyond Transactional
Sequenzy handles transactional sends, marketing campaigns, automated sequences, and subscriber management from one API. Native Stripe integration for SaaS.
Wrapping Up
- Flask-Mail vs API providers and when to use each
- Flask routes for API and form-based sending
- Jinja2 templates for email HTML
- Celery for background sending
Pick your provider, copy the patterns, and start sending.