← Back to Blog
Developer Guides14 May 2026 · NexusProMail Team

How to Send Email with Python: Transactional Email via REST API

A complete guide to sending transactional email from Python using the NexusProMail REST API — environment config, send helper, error handling, webhook processing and production checklist.

Python is one of the most common languages for backend services that send transactional email. This guide shows how to integrate the NexusProMail REST API into a Python application with production-ready patterns.

Prerequisites

  • Python 3.8 or later
  • A NexusProMail account — sandbox key available immediately on signup, no credit card required
  • A verified sending domain for production sends (DKIM + SPF)

Step 1 — Install dependencies

pip install requests python-dotenv

Step 2 — Configure environment variables

# .env — never commit this file
NEXUSPROMAIL_API_KEY=your_sandbox_key_here
NEXUSPROMAIL_API_URL=https://app.nexuspromail.com/api
FROM_EMAIL=hello@mail.yourdomain.com
FROM_NAME=Your App

Step 3 — Create a reusable send helper

import os
import requests
from dotenv import load_dotenv

load_dotenv()

API_KEY = os.environ["NEXUSPROMAIL_API_KEY"]
API_URL = os.environ.get("NEXUSPROMAIL_API_URL", "https://app.nexuspromail.com/api")
FROM_EMAIL = os.environ["FROM_EMAIL"]
FROM_NAME = os.environ.get("FROM_NAME", "My App")
SEND_URL = f"{API_URL}/v1/transactional/send"


def send_email(to: str, subject: str, html: str, text: str = None) -> dict:
    payload = {
        "to": {"email": to},
        "from": {"email": FROM_EMAIL, "name": FROM_NAME},
        "subject": subject,
        "html": html,
    }
    if text:
        payload["text"] = text

    try:
        response = requests.post(
            SEND_URL,
            json=payload,
            headers={
                "Authorization": f"Bearer {API_KEY}",
                "Content-Type": "application/json",
            },
            timeout=10
        )
    except requests.exceptions.Timeout:
        return {"success": False, "code": "TIMEOUT"}
    except requests.exceptions.RequestException as e:
        return {"success": False, "code": "NETWORK_ERROR", "error": str(e)}

    if response.status_code == 200:
        return {"success": True, "message_id": response.json().get("message_id")}

    if response.status_code == 422:
        body = response.json()
        if body.get("error") == "suppressed":
            return {"success": False, "code": "SUPPRESSED"}
        return {"success": False, "code": "VALIDATION", "error": body.get("message")}

    if response.status_code == 429:
        return {"success": False, "code": "RATE_LIMITED",
                "retry_after": int(response.headers.get("Retry-After", "60"))}

    return {"success": False, "code": "API_ERROR", "status": response.status_code}

Step 4 — Send a password reset email

def send_password_reset(user_email: str, reset_token: str) -> bool:
    reset_url = f"https://yourapp.com/reset?token={reset_token}"

    result = send_email(
        to=user_email,
        subject="Reset your password",
        html=f"

Click to reset: Reset password

Expires in 1 hour.

", text=f"Reset your password: {reset_url}\n\nExpires in 1 hour.", ) if not result["success"]: if result["code"] == "SUPPRESSED": print(f"Skipped suppressed address: {user_email}") return False if result["code"] == "RATE_LIMITED": raise Exception(f"Rate limited — retry after {result['retry_after']}s") raise Exception(f"Email send failed: {result}") return True

Step 5 — Retry logic with exponential backoff

import time

def send_with_retry(to: str, subject: str, html: str, max_retries: int = 3) -> dict:
    for attempt in range(max_retries + 1):
        result = send_email(to, subject, html)

        if result["success"]:
            return result

        # Terminal outcomes — never retry
        if result["code"] in ("SUPPRESSED", "VALIDATION"):
            return result

        if result["code"] == "RATE_LIMITED":
            wait = result.get("retry_after", 30) * (2 ** attempt)
            time.sleep(wait)
            continue

        if result["code"] == "API_ERROR":
            time.sleep(min(2 ** attempt * 5, 60))
            continue

        return result

    raise Exception(f"Max retries exceeded for {to}")

Step 6 — Handle webhooks with Flask

import hashlib
import hmac
from flask import Flask, request, jsonify

app = Flask(__name__)
WEBHOOK_SECRET = os.environ["WEBHOOK_SECRET"]


def verify_signature(raw_body: bytes, signature: str) -> bool:
    expected = hmac.new(
        WEBHOOK_SECRET.encode("utf-8"), raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)


@app.route("/webhooks/email", methods=["POST"])
def handle_webhook():
    sig = request.headers.get("X-NexusProMail-Signature", "")
    if not verify_signature(request.data, sig):
        return jsonify({"error": "Invalid signature"}), 401

    event = request.get_json()

    if event["type"] == "bounced.hard":
        suppress_address(event["data"]["email"])
    elif event["type"] == "complained":
        suppress_address(event["data"]["email"], reason="complaint")
    elif event["type"] == "unsubscribed":
        update_subscription(event["data"]["email"], subscribed=False)

    return "", 200  # Always acknowledge promptly

Production checklist

  • API key in environment variable — never in source code
  • Sandbox key for development, production key for live sends
  • Sending domain verified (DKIM + SPF) in NexusProMail dashboard
  • Hard bounce handler suppresses immediately and does not retry
  • Complaint handler suppresses immediately
  • Webhook signature verification implemented
  • 429 responses read Retry-After header and use exponential backoff
  • 422/suppression responses never trigger retries
  • Domain warming initiated — see domain warming guide

Further reading

Related reading

Email deliverability guideGDPR complianceTransactional email API

Start sending with NexusProMail

Launch email campaigns and transactional emails from one platform.

Start FreeView Pricing

Free plan · No credit card required · GDPR-compliant · Built in Finland

How to Send Email with Python: Transactional Email via REST API | NexusProMail