Sending

Send email with Python

A step-by-step guide to sending transactional email from a Python application using the Rovela API and the requests library. Covers a minimal send, retries with idempotency keys, and production patterns.

Rovela's API is a single JSON endpoint, so sending from Python is a straightforward HTTP request. This guide walks through a minimal send, then adds retries with idempotency keys so a network blip never produces a duplicate message.

Prerequisites

You need a Rovela account (free, no card), an API key from the dashboard, and a verified sending domain. Python 3.7 or later is assumed throughout, with the requests library installed. The same approach works with urllib from the standard library, or with httpx for async, by swapping the HTTP call.

import os
import requests

res = requests.post(
    "https://api.rovela.dev/emails",
    headers={
        "Authorization": f"Bearer {os.environ['ROVELA_API_KEY']}",
        "Content-Type": "application/json",
    },
    json={
        "from": "hello@yourdomain.com",
        "to": "user@example.com",
        "subject": "Welcome aboard",
        "html": "<p>Thanks for signing up. Your account is ready.</p>",
    },
)
print("Sent:", res.json()["id"])

Send your first email

Import requests and os, read your API key from an environment variable, and POST a JSON body to the /emails endpoint. The from address must be on a domain you have verified. The response includes an id you can use to look up the delivery status later.

Retries with idempotency keys

Network requests fail, and the safe response to a timeout is to retry. But a retry can produce a second message if the first one actually went through and only the response was lost. An idempotency key fixes this: send the same key on every attempt, and Rovela returns the original response without dispatching a duplicate. Generate the key once before the retry loop, not inside it.

import os
import time
import uuid
import requests

def send_with_retry(payload, retries=3):
    key = str(uuid.uuid4())
    for i in range(retries + 1):
        res = requests.post(
            "https://api.rovela.dev/emails",
            headers={
                "Authorization": f"Bearer {os.environ['ROVELA_API_KEY']}",
                "Content-Type": "application/json",
                "Idempotency-Key": key,  # same key on every attempt
            },
            json=payload,
        )
        if res.status_code < 500:
            return res                    # 2xx or 4xx — stop
        time.sleep(min(1000 * 2 ** i, 8000) / 1000)  # backoff, capped
    raise RuntimeError(f"send failed after {retries} retries")

Environment variables

Never put an API key in source code. Use os.environ and load it from a .env file with python-dotenv in development, or from your deployment platform's secret store in production. Rotate the key from the Rovela dashboard if it is ever exposed; the new key takes effect immediately and the old one stops working.

Common questions

Do I need an SDK, or can I use plain requests?
Plain requests works fine. Rovela's API is a single JSON endpoint; there is no SDK required. An SDK can reduce boilerplate if you are integrating from a larger codebase, but it is optional.
How do I send to multiple recipients?
Pass a list to the 'to' field: ["alice@example.com", "bob@example.com"]. Each address receives an individual copy of the message; recipients do not see each other's addresses. The to, cc, and bcc fields all accept strings or lists.
How do I send HTML and a plain-text fallback?
Include both the 'html' and 'text' fields in the request body. Mail clients that cannot render HTML display the text version. Rovela sends both parts in a multipart/alternative message automatically.
Can I use httpx or aiohttp for async sending?
Yes. Replace requests.post with the equivalent httpx or aiohttp call. The request body and headers are identical; only the HTTP client changes.

Ready to send your first email?

Rovela gives you a plain HTTP API, business mailboxes on your own domain, idempotency keys, and delivery webhooks — all on a free plan with no card required.