Send with your stack

Send emails with Python

Send email from Python with the official mailblastr package — zero dependencies, configured module-level like stripe or resend.

The official `mailblastr` package is the fastest way to send email from Python. It has zero dependencies (standard library urllib + json only) and is configured module-level — set mailblastr.api_key once, then call the resource classes. There is no client object to construct and pass around.

This guide sends a single email with mailblastr.Emails.send(...). The package also sets the required User-Agent header on every request for you, retries 429/503 for you, and raises typed errors instead of handing back raw HTTP.

Prerequisites

  1. A verified domain for your from address. (guide)
  2. An API key (mb_...), read from MAILBLASTR_API_KEY. (Authentication)
  3. Python 3.8 or newer — the package declares requires-python >= 3.8.

Install

pip install mailblastr

Configure

Assign your key to mailblastr.api_key once, at import time or during app startup. Read it from the environment so the secret never lands in source control.

mailer.py
import os

import mailblastr

mailblastr.api_key = os.environ["MAILBLASTR_API_KEY"]

Two more module-level knobs are optional: mailblastr.timeout (per-request seconds, default 30) and mailblastr.max_retries (automatic retries of 429/503 only, default 2; set 0 to disable). Because only those two statuses are retried, a send is never duplicated by a retry.

Send an email

Every field of the send body is a plain dict key — from is a Python keyword, so the payload is a dict rather than keyword arguments. to accepts a single address or a list.

send.py
import os

import mailblastr

mailblastr.api_key = os.environ["MAILBLASTR_API_KEY"]

email = mailblastr.Emails.send({
    "from": "Acme <hello@yourdomain.com>",
    "to": ["delivered@mailblastr.dev"],
    "subject": "Hello from Python",
    "html": "<p>Sent with the mailblastr package 🐍</p>",
})

print("Sent email", email["id"])
delivered@mailblastr.dev is the MailBlastr simulator: the send is accepted, recorded, and marked delivered without touching a real inbox. Swap it for your own address once the first call succeeds — and never use @example.com, which the API rejects with 422.

Handling the response

Every method returns the parsed JSON response as a dict, so a successful send gives you email["id"]. A non-2xx answer raises mailblastr.MailblastrError, which carries the { statusCode, name, message } envelope — branch on err.name, never on err.message (messages are scrubbed server-side and are not a stable contract).

import time

try:
    email = mailblastr.Emails.send(params)
    print("Sent email", email["id"])
except mailblastr.MailblastrError as err:
    print(err.status_code, err.name, err.message)

    if err.name == "daily_quota_exceeded":
        # Plan/quota rejections describe the cap that was hit.
        print(err.limit["used"], err.limit["limit"], err.limit["next_plan"])

    if err.retry_after:
        time.sleep(err.retry_after)

    print(err.body)   # the full parsed error body

A transport failure (or a missing key) raises the same exception with status_code == 0 and name of network_error or missing_api_key, so one except clause covers both.

Retry safely with an idempotency key

Pass options={"idempotency_key": "..."} so replaying a request returns the original response instead of sending twice. Only Emails.send and Batch.send honour it — see Idempotency.

mailblastr.Emails.send(params, options={"idempotency_key": "order-123"})

# Up to 100 emails in one request (no attachments, no scheduled_at).
mailblastr.Batch.send([
    {"from": "hello@yourdomain.com", "to": ["delivered@mailblastr.dev"], "subject": "Hi A", "html": "<p>A</p>"},
    {"from": "hello@yourdomain.com", "to": ["delivered@mailblastr.dev"], "subject": "Hi B", "html": "<p>B</p>"},
], options={"idempotency_key": "orders-2026-08-19"})

Schedule instead of sending now

Add scheduled_at (ISO 8601) to hand the send to the scheduler. A scheduled send goes through the real delivery path, so it cannot target the simulator address — use one of your own recipients.

scheduled = mailblastr.Emails.send({
    "from": "Acme <hello@yourdomain.com>",
    "to": ["you@yourdomain.com"],
    "subject": "Tomorrow morning",
    "html": "<p>Queued ahead of time.</p>",
    "scheduled_at": "2026-09-01T09:00:00Z",
})

mailblastr.Emails.update(scheduled["id"], {"scheduled_at": "2026-09-01T14:00:00Z"})   # reschedule
mailblastr.Emails.cancel(scheduled["id"])                                            # or call it off
Keep the mb_ API key server-side — load it from an environment variable or secrets manager, never commit it, and never ship it in a client-side script or notebook you share. Anyone holding the key can send email as you.

Next steps