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
- API integration guide — authentication, rate limits, SMTP vs REST, error reference
- Email webhooks guide — event types, signature verification, idempotency
- Email subdomain strategy — protecting your root domain
- Domain warming guide — week-by-week schedule and safety thresholds