Skip to main content
New to the Lettr Python SDK? Start with the Python Quickstart to learn the basics, then return here for Flask-specific integration patterns.
Send transactional emails from your Flask applications using the official Lettr Python SDK. Flask’s flexible architecture makes it easy to integrate Lettr for user notifications, password resets, order confirmations, and other transactional emails.

Prerequisites

Before you begin, make sure you have: You’ll also need:
  • Python 3.8 or later installed
  • Flask web framework
  • A verified sending domain in your Lettr dashboard

Quick Setup

Get started in three quick steps: install dependencies, configure Flask, and send.
1

Install dependencies

pip install lettr flask python-dotenv
This installs the Lettr SDK, Flask web framework, and python-dotenv for environment variable management.
2

Configure environment

Create a .env file in your project root:
LETTR_API_KEY=lttr_your_api_key_here
FLASK_ENV=development
Add .env to your .gitignore to prevent committing your API key to version control.
3

Create Flask application

import os
from flask import Flask, jsonify, request
from dotenv import load_dotenv
import lettr

load_dotenv()

app = Flask(__name__)
client = lettr.Lettr(os.environ["LETTR_API_KEY"])

@app.route("/send-email", methods=["POST"])
def send_email():
    data = request.json

    response = client.emails.send(
        from_email="notifications@yourdomain.com",
        to=[data["to"]],
        subject=data["subject"],
        html=data["html"],
    )

    return jsonify({
        "success": True,
        "request_id": response.request_id,
        "accepted": response.accepted,
    })

if __name__ == "__main__":
    app.run()
Run with python app.py or flask run.
The sender domain must be verified in your Lettr dashboard before you can send emails. Sending from an unverified domain returns a validation error.

Flask Application Structure

For production applications, organize your code with a proper structure:
myapp/
├── app.py              # Application factory
├── config.py           # Configuration
├── services/
│   └── email.py        # Email service
├── routes/
│   ├── __init__.py
│   ├── auth.py         # Authentication routes
│   └── api.py          # API routes
├── .env                # Environment variables
└── requirements.txt    # Dependencies

Configuration

Create a config.py file for application configuration:
import os
from dotenv import load_dotenv

load_dotenv()

class Config:
    """Base configuration."""
    LETTR_API_KEY = os.environ.get("LETTR_API_KEY")
    MAIL_FROM_ADDRESS = os.environ.get("MAIL_FROM_ADDRESS", "notifications@yourdomain.com")
    MAIL_FROM_NAME = os.environ.get("MAIL_FROM_NAME", "My Application")

class DevelopmentConfig(Config):
    """Development configuration."""
    DEBUG = True

class ProductionConfig(Config):
    """Production configuration."""
    DEBUG = False

config = {
    "development": DevelopmentConfig,
    "production": ProductionConfig,
    "default": DevelopmentConfig,
}

Email Service

Create an email service in services/email.py:
import lettr
from flask import current_app

class EmailService:
    """Email service for sending transactional emails."""

    def __init__(self, client=None):
        self.client = client or lettr.Lettr(current_app.config["LETTR_API_KEY"])

    def send_welcome_email(self, recipient, name):
        """Send a welcome email to a new user."""
        response = self.client.emails.send(
            from_email=current_app.config["MAIL_FROM_ADDRESS"],
            from_name=current_app.config["MAIL_FROM_NAME"],
            to=[recipient],
            subject=f"Welcome to {current_app.config['MAIL_FROM_NAME']}, {name}!",
            html=f"""
                <h1>Welcome, {name}!</h1>
                <p>Thanks for signing up. We're excited to have you on board.</p>
                <p>If you have any questions, feel free to reply to this email.</p>
            """,
            metadata={
                "email_type": "welcome",
                "user_email": recipient,
            },
        )
        return response.request_id

    def send_password_reset(self, recipient, reset_token):
        """Send a password reset email."""
        reset_url = f"https://yourdomain.com/reset-password?token={reset_token}"

        response = self.client.emails.send(
            from_email=current_app.config["MAIL_FROM_ADDRESS"],
            from_name=current_app.config["MAIL_FROM_NAME"],
            to=[recipient],
            subject="Reset your password",
            html=f"""
                <h1>Reset your password</h1>
                <p>Click the link below to reset your password:</p>
                <p><a href="{reset_url}">Reset Password</a></p>
                <p>This link expires in 1 hour.</p>
                <p>If you didn't request this, please ignore this email.</p>
            """,
            metadata={
                "email_type": "password_reset",
                "user_email": recipient,
            },
        )
        return response.request_id

    def send_order_confirmation(self, recipient, order_id, items, total):
        """Send an order confirmation email."""
        items_html = "".join([
            f"<li>{item['name']} - ${item['price']}</li>"
            for item in items
        ])

        response = self.client.emails.send(
            from_email=current_app.config["MAIL_FROM_ADDRESS"],
            from_name=current_app.config["MAIL_FROM_NAME"],
            to=[recipient],
            subject=f"Order Confirmation #{order_id}",
            html=f"""
                <h1>Order Confirmation</h1>
                <p>Thank you for your order!</p>
                <h2>Order #{order_id}</h2>
                <ul>{items_html}</ul>
                <p><strong>Total: ${total}</strong></p>
            """,
            metadata={
                "email_type": "order_confirmation",
                "order_id": order_id,
                "user_email": recipient,
            },
        )
        return response.request_id

Application Factory

Create an application factory in app.py:
from flask import Flask
import lettr
from config import config

def create_app(config_name="default"):
    """Application factory pattern."""
    app = Flask(__name__)
    app.config.from_object(config[config_name])

    # Initialize Lettr client as an app extension
    app.lettr_client = lettr.Lettr(app.config["LETTR_API_KEY"])

    # Register blueprints
    from routes.auth import auth_bp
    from routes.api import api_bp

    app.register_blueprint(auth_bp, url_prefix="/auth")
    app.register_blueprint(api_bp, url_prefix="/api")

    return app

if __name__ == "__main__":
    app = create_app()
    app.run()

Routes

Create authentication routes in routes/auth.py:
from flask import Blueprint, request, jsonify, current_app
from services.email import EmailService

auth_bp = Blueprint("auth", __name__)

@auth_bp.route("/register", methods=["POST"])
def register():
    """Register a new user and send welcome email."""
    data = request.json
    email = data.get("email")
    name = data.get("name")
    password = data.get("password")

    # ... validate data and create user ...

    # Send welcome email
    try:
        email_service = EmailService()
        request_id = email_service.send_welcome_email(email, name)

        return jsonify({
            "success": True,
            "message": "Registration successful",
            "email_request_id": request_id,
        }), 201
    except Exception as e:
        current_app.logger.error(f"Failed to send welcome email: {e}")
        return jsonify({
            "success": True,
            "message": "Registration successful, but email failed to send",
        }), 201

@auth_bp.route("/forgot-password", methods=["POST"])
def forgot_password():
    """Send password reset email."""
    data = request.json
    email = data.get("email")

    # ... generate reset token ...
    reset_token = "example_token_123"

    try:
        email_service = EmailService()
        request_id = email_service.send_password_reset(email, reset_token)

        return jsonify({
            "success": True,
            "message": "Password reset email sent",
            "request_id": request_id,
        })
    except Exception as e:
        current_app.logger.error(f"Failed to send password reset: {e}")
        return jsonify({
            "success": False,
            "message": "Failed to send email",
        }), 500

Advanced Features

Using Templates

Send emails using Lettr-managed templates:
def send_template_email(self, recipient, template_id, merge_tags):
    """Send an email using a Lettr template."""
    response = self.client.emails.send(
        from_email=current_app.config["MAIL_FROM_ADDRESS"],
        to=[recipient],
        template_id=template_id,
        merge_tags=merge_tags,
    )
    return response.request_id

# Usage
email_service = EmailService()
email_service.send_template_email(
    recipient="user@example.com",
    template_id="welcome-email",
    merge_tags={
        "name": "John Doe",
        "verify_url": "https://example.com/verify/abc123",
    },
)
See the Python Quickstart for more details on templates, attachments, and other SDK features.

Background Task Queue with Celery

For production applications, send emails asynchronously using Celery:
pip install celery redis
Create tasks.py:
from celery import Celery
import lettr
import os

celery = Celery(__name__, broker=os.environ["REDIS_URL"])

@celery.task
def send_email_task(from_email, to, subject, html, **kwargs):
    """Asynchronous email sending task."""
    client = lettr.Lettr(os.environ["LETTR_API_KEY"])
    response = client.emails.send(
        from_email=from_email,
        to=to,
        subject=subject,
        html=html,
        **kwargs,
    )
    return response.request_id
Use in routes:
from tasks import send_email_task

@app.route("/register", methods=["POST"])
def register():
    data = request.json
    # ... create user ...

    # Queue email to be sent in background
    send_email_task.delay(
        from_email="notifications@yourdomain.com",
        to=[data["email"]],
        subject="Welcome!",
        html="<h1>Welcome to our platform!</h1>",
    )
    return jsonify({"success": True})

Error Handling

Implement error handling in your routes:
from lettr.exceptions import LettrException, LettrValidationException

@app.route("/send-email", methods=["POST"])
def send_email():
    try:
        response = client.emails.send(...)
        app.logger.info(f"Email sent: {response.request_id}")
        return jsonify({"success": True, "request_id": response.request_id})
    except LettrValidationException as e:
        app.logger.error(f"Validation error: {e.message}")
        return jsonify({"success": False, "error": "Invalid email parameters"}), 422
    except LettrException as e:
        app.logger.error(f"Email service error: {e.message}")
        return jsonify({"success": False, "error": "Email service error"}), 500
See Error Handling in the Python Quickstart for comprehensive error handling patterns.

Testing

Create unit tests for your email service using Flask’s test client:
import unittest
from unittest.mock import Mock, patch
from app import create_app

class TestAuthRoutes(unittest.TestCase):
    def setUp(self):
        self.app = create_app("testing")
        self.client = self.app.test_client()

    @patch("services.email.EmailService.send_welcome_email")
    def test_register_endpoint(self, mock_send_email):
        """Test user registration endpoint."""
        mock_send_email.return_value = "test-request-id"

        response = self.client.post("/auth/register", json={
            "email": "test@example.com",
            "name": "Test User",
            "password": "password123",
        })

        self.assertEqual(response.status_code, 201)
        data = response.get_json()
        self.assertTrue(data["success"])

Best Practices

Use Flask Configuration

Store email settings in Flask configuration:
class Config:
    LETTR_API_KEY = os.environ.get("LETTR_API_KEY")
    MAIL_FROM_ADDRESS = "notifications@yourdomain.com"
    MAIL_FROM_NAME = "My Application"
    MAIL_REPLY_TO = "support@yourdomain.com"
    MAIL_ENABLE_TRACKING = True

Logging

Use Flask’s built-in logger:
@app.route("/send-email", methods=["POST"])
def send_email():
    try:
        response = client.emails.send(...)
        app.logger.info(
            f"Email sent successfully",
            extra={
                "request_id": response.request_id,
                "to": data["to"],
            },
        )
        return jsonify({"success": True})
    except Exception as e:
        app.logger.error(f"Failed to send email: {e}")
        return jsonify({"success": False}), 500

Rate Limiting

Use Flask-Limiter to prevent abuse:
pip install flask-limiter
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

limiter = Limiter(
    app=app,
    key_func=get_remote_address,
    default_limits=["200 per day", "50 per hour"],
)

@app.route("/send-email", methods=["POST"])
@limiter.limit("10 per minute")
def send_email():
    # ... send email ...
    pass

What’s Next