Transactional Email with Resend
Transactional Email with Resend
Every real app needs to send email — welcome messages, password resets, order confirmations. This tutorial shows how to integrate Resend into Flask cleanly, keeping your routes thin and email logic in a reusable helper.
Why Resend?
Resend is a modern email API built for developers:
- Simple Python SDK — one function call to send
- Reliable delivery with SPF/DKIM handled automatically
- Free tier — 3,000 emails/month, 100/day
- Dashboard with delivery logs and per-email analytics
No SMTP port confusion, no TLS certificate setup, no MX records to manage yourself.
Setup
Install the SDK:
pip install resend
Add your API key to .env:
RESEND_API_KEY=re_your_key_here
[email protected]
Get your API key at resend.com → API Keys → Create API Key.
Create the Email Helper
Create utils/email.py:
import os
import resend
from flask import render_template_string
resend.api_key = os.environ.get("RESEND_API_KEY")
FROM_EMAIL = os.environ.get("FROM_EMAIL", "[email protected]")
def send_email(to, subject, html):
"""Send a single email. Returns True on success, False on failure."""
try:
resend.Emails.send({
"from": FROM_EMAIL,
"to": to,
"subject": subject,
"html": html
})
return True
except Exception as e:
print(f"Email error: {e}")
return False
def send_welcome_email(user_email, user_name):
"""Send a welcome email to a new user."""
html = render_template_string("""
<h1>Welcome, {{ name }}!</h1>
<p>Thanks for joining. Your account is ready.</p>
<p>— The Team</p>
""", name=user_name)
return send_email(
to=user_email,
subject=f"Welcome, {user_name}!",
html=html
)
Key design decisions:
send_email()is generic — accepts any HTML string- Specific helpers like
send_welcome_email()build on top of it - Returns
True/Falseso routes can handle failures without crashing - The API key is loaded once at module import time
Calling It From a Flask Route
from utils.email import send_welcome_email
@app.route("/register", methods=["POST"])
def register():
name = request.form.get("name")
email = request.form.get("email")
password = request.form.get("password")
hashed = generate_password_hash(password)
with get_db() as conn:
with conn.cursor() as cur:
cur.execute(
"INSERT INTO users (name, email, password_hash) VALUES (%s, %s, %s)",
(name, email, hashed)
)
conn.commit()
# Send welcome email — failure does not crash the request
send_welcome_email(email, name)
return redirect("/dashboard")
The route stays thin. Email logic lives entirely in utils/email.py.
Richer HTML Email Template
For fully branded emails, define the HTML as a template string:
WELCOME_TEMPLATE = """
<!DOCTYPE html>
<html>
<body style="font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
<h2 style="color: #333;">Welcome, {{ name }}!</h2>
<p>Your account has been created successfully.</p>
<a href="{{ app_url }}/dashboard"
style="background: #333; color: white; padding: 12px 24px;
text-decoration: none; border-radius: 4px; display: inline-block;">
Go to Dashboard
</a>
<p style="color: #999; font-size: 12px; margin-top: 32px;">
You received this because you signed up at {{ app_url }}.
</p>
</body>
</html>
"""
def send_welcome_email(user_email, user_name):
html = render_template_string(
WELCOME_TEMPLATE,
name=user_name,
app_url=os.environ.get("APP_URL", "https://yourapp.com")
)
return send_email(user_email, f"Welcome, {user_name}!", html)
Skip Real Sends in Development
During development, log the email instead of sending it:
def send_email(to, subject, html):
if os.environ.get("FLASK_ENV") == "development":
print(f"[DEV EMAIL] To: {to} | Subject: {subject}")
return True
try:
resend.Emails.send({
"from": FROM_EMAIL,
"to": to,
"subject": subject,
"html": html
})
return True
except Exception as e:
print(f"Email error: {e}")
return False
Add FLASK_ENV=development to your .env to skip real sends locally.
AI Prompt That Generated This
"Create utils/email.py for a Flask app using the Resend Python SDK. Include a generic send_email(to, subject, html) function that returns True/False and a send_welcome_email(email, name) helper. Load RESEND_API_KEY from os.environ. Handle exceptions without crashing the caller."
Next Steps
- Build Password Reset Flow using this helper for reset link emails
- Scale sends with Background Tasks with Celery for bulk operations
Was this helpful?
Get More Flask Vibe Tutorials
Join 1,000+ developers getting weekly Flask tips and AI-friendly code patterns.
No spam. Unsubscribe anytime.