Back to Blog

How to Send Emails in Flask (2026 Guide)

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 requests
Terminal
pip install resend
Terminal
pip install sendgrid

Create 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=production

3. Use Gunicorn

gunicorn app:app -w 4

Beyond Transactional

Sequenzy handles transactional sends, marketing campaigns, automated sequences, and subscriber management from one API. Native Stripe integration for SaaS.

Wrapping Up

  1. Flask-Mail vs API providers and when to use each
  2. Flask routes for API and form-based sending
  3. Jinja2 templates for email HTML
  4. Celery for background sending

Pick your provider, copy the patterns, and start sending.