Skip to main content

Official Python SDK for the Maielr email API

Project description

Maielr Python SDK

Official Python SDK for the Maielr email API. Send transactional and marketing emails with enterprise-grade deliverability.

Installation

pip install maielr

Requirements: Python 3.8+

Quick Start

from maielr import Maielr

client = Maielr("your_api_key")

result = client.send(
    from_email="sender@yourdomain.com",
    to="recipient@example.com",
    subject="Hello from Maielr",
    html="<h1>Hello World</h1>",
)

print(result)  # {"id": "...", "status": "queued", "message": "..."}

Features

  • Sync and async clients -- Maielr and AsyncMailer
  • Full type hints (PEP 484) with py.typed marker
  • Automatic retries with exponential backoff and jitter on 429 and 5xx errors
  • Structured error hierarchy with request IDs for debugging
  • Webhook signature verification with HMAC-SHA256 and replay attack prevention
  • Auto-pagination iterators for list endpoints
  • Idempotency keys to prevent duplicate operations
  • Per-request timeouts to override the global timeout
  • Debug logging with configurable verbosity
  • Custom HTTP client support for advanced configurations
  • Security headers including SDK version and request ID tracking
  • Thread-safe synchronous client
  • Python 3.8+ compatible

Authentication

Get your API key from the Maielr Dashboard.

client = Maielr("your_api_key")

Configuration

client = Maielr(
    api_key="your_api_key",
    base_url="https://your-custom-relay.example.com",  # custom base URL
    timeout=60.0,       # 60 second timeout (default: 30s)
    max_retries=5,      # 5 retries (default: 3)
    debug=True,         # enable debug logging (default: False)
)

Context Manager

with Maielr("your_api_key") as client:
    client.send(...)
# Client is automatically closed

Sending Email

result = client.send(
    from_email="sender@yourdomain.com",
    from_name="Acme Support",
    to=["alice@example.com", "bob@example.com"],
    cc="manager@example.com",
    bcc=["archive@example.com"],
    subject="Monthly Report",
    html="<h1>Your Report</h1><p>See attached.</p>",
    text="Your Report -- see attached.",
    reply_to="support@yourdomain.com",
    headers={"X-Campaign-Id": "monthly-report"},
    attachments=[
        {
            "filename": "report.pdf",
            "content": "<base64-encoded-content>",
            "content_type": "application/pdf",
        }
    ],
)

Idempotency Keys

Prevent duplicate sends by passing an idempotency key. If the server has already processed a request with the same key, it returns the original response instead of creating a duplicate.

# Generate a key
idem_key = Maielr.generate_idempotency_key()

# Pass it to any POST method
result = client.send(
    from_email="sender@yourdomain.com",
    to="recipient@example.com",
    subject="Important",
    html="<p>This will only be sent once.</p>",
    idempotency_key=idem_key,
)

# Also works with add_suppression and batch_send
client.add_suppression("user@example.com", idempotency_key=idem_key)

Per-Request Timeouts

Override the global timeout for individual requests:

# This request gets 5 seconds instead of the default 30
result = client.send(
    from_email="sender@yourdomain.com",
    to="recipient@example.com",
    subject="Quick",
    html="<p>Hurry</p>",
    timeout=5.0,
)

# Works on all methods
status = client.get_status("msg_123", timeout=10.0)
bounces = client.list_bounces(days=7, timeout=60.0)

Auto-Pagination

Iterate over all results without manual pagination:

# Sync -- generator that yields individual messages
for msg in client.list_messages_auto(status="delivered", page_size=50):
    print(msg["id"], msg["status"])

# Async -- async iterator
async for msg in await async_client.list_messages_auto(page_size=50):
    print(msg["id"])

Webhook Signature Verification

Verify incoming webhook payloads to ensure they are authentic and not replayed:

from maielr import Maielr, WebhookVerificationError

client = Maielr("your_api_key")

# In your webhook handler (e.g., Flask, FastAPI, Django)
def handle_webhook(request):
    payload = request.body  # raw bytes
    signature = request.headers["X-Maielr-Signature"]
    webhook_secret = "whsec_your_webhook_secret"

    try:
        # Verify + parse in one step
        event = client.webhooks.construct_event(
            payload=payload,
            signature=signature,
            secret=webhook_secret,
            tolerance=300,  # reject signatures older than 5 minutes
        )
        print(f"Received event: {event['type']}")
    except WebhookVerificationError as e:
        print(f"Verification failed: {e.message}")
        return 400

    # Or verify only (returns True or raises)
    client.webhooks.verify_signature(payload, signature, webhook_secret)

The signature format is t=<unix_timestamp>,v1=<hmac_sha256_hex>. The signed content is <timestamp>.<raw_payload>.

Debug Logging

Enable debug mode to see request/response details:

import logging

# Option 1: Use the debug flag
client = Maielr("your_api_key", debug=True)

# Option 2: Configure the logger directly
logging.getLogger("maielr").setLevel(logging.DEBUG)
logging.getLogger("maielr").addHandler(logging.StreamHandler())

Debug output includes:

  • Request method and URL
  • Attempt number and request ID
  • Response status code and timing
  • Retry decisions and delays

Custom HTTP Client

Pass a pre-configured httpx.Client for advanced scenarios (proxies, custom certificates, connection pooling):

import httpx

custom_client = httpx.Client(
    proxies="http://proxy.example.com:8080",
    verify="/path/to/custom-ca-bundle.crt",
    limits=httpx.Limits(max_connections=50),
)

client = Maielr(
    api_key="your_api_key",
    http_client=custom_client,
)

# The SDK adds auth headers but uses your client for transport
result = client.send(...)

# When done, close your custom client separately
custom_client.close()

For async:

import httpx

custom_async = httpx.AsyncClient(
    proxies="http://proxy.example.com:8080",
)

client = AsyncMailer(
    api_key="your_api_key",
    http_client=custom_async,
)

Async Usage

Every method is available asynchronously via AsyncMailer:

import asyncio
from maielr import AsyncMailer

async def main():
    async with AsyncMailer("your_api_key") as client:
        result = await client.send(
            from_email="sender@yourdomain.com",
            to="recipient@example.com",
            subject="Async Hello",
            html="<h1>Hello from async!</h1>",
            idempotency_key=AsyncMailer.generate_idempotency_key(),
        )

        # Auto-pagination works async too
        async for msg in await client.list_messages_auto():
            print(msg["id"])

asyncio.run(main())

Error Handling

from maielr import (
    Maielr,
    MailerError,
    AuthError,
    RateLimitError,
    ValidationError,
    NotFoundError,
)

client = Maielr("your_api_key")

try:
    result = client.send(
        from_email="sender@yourdomain.com",
        to="recipient@example.com",
        subject="Hello",
        html="<h1>Hello</h1>",
    )
except AuthError as e:
    print(f"Auth failed: {e.message}")
except RateLimitError as e:
    print(f"Rate limited. Retry after {e.retry_after}s")
except ValidationError as e:
    print(f"Validation error: {e.message}")
    print(f"Details: {e.errors}")
except NotFoundError as e:
    print(f"Not found: {e.message}")
except MailerError as e:
    print(f"API error ({e.status_code}): {e.message}")
    print(f"Request ID: {e.request_id}")  # client-generated or server-assigned

Error Hierarchy

MailerError (base)
  APIError              (unmapped status codes)
  AuthError             (401, 403)
  ValidationError       (400)
  NotFoundError         (404)
  RateLimitError        (429)
  ServerError           (5xx)
  ConnectionError       (network failures)
  TimeoutError          (request timeouts)

WebhookVerificationError  (signature verification failures)

All errors include a request_id attribute -- either the server-assigned ID from the x-request-id response header or the client-generated X-Request-ID sent with the request.

Retry Behavior

The SDK automatically retries on:

  • HTTP 429 -- Rate limit exceeded (respects Retry-After header)
  • HTTP 500, 502, 503, 504 -- Server errors
  • Network connection failures
  • Request timeouts

Retries use exponential backoff with jitter:

delay = base_delay * (2 ^ attempt) + random(0, base_delay)

Where base_delay is 1 second. The delay is capped at 30 seconds. For 429 responses, the Retry-After header takes precedence.

Security Headers

Every request automatically includes:

  • X-SDK-Version: maielr-python/1.0.0 -- SDK identification
  • X-SDK-Timestamp: <ISO 8601> -- Request timestamp
  • X-Request-ID: <uuid4> -- Unique request identifier for tracing

Thread Safety

The synchronous Maielr client is thread-safe because it uses httpx.Client, which is designed for concurrent use across threads.

The AsyncMailer client should be used within a single asyncio event loop. For multi-threaded async applications, create one AsyncMailer per event loop.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

maielr-1.0.0.tar.gz (21.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

maielr-1.0.0-py3-none-any.whl (17.8 kB view details)

Uploaded Python 3

File details

Details for the file maielr-1.0.0.tar.gz.

File metadata

  • Download URL: maielr-1.0.0.tar.gz
  • Upload date:
  • Size: 21.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for maielr-1.0.0.tar.gz
Algorithm Hash digest
SHA256 f237bff6e243be0a7d479f3b9be97ec72c79e474c378b28b5d9b2a58cfec649e
MD5 d8cc7f9e1306ca7891608125c4425df0
BLAKE2b-256 a3ae67370cc38699c90e41e9d4ff66d52cc498f2592e426252a17715e9752473

See more details on using hashes here.

File details

Details for the file maielr-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: maielr-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 17.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for maielr-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 98a69f33a4c2bece7635c156aa2a4ae99bcc002e807011a50b52141a262f5863
MD5 6d3232705895454868ea491a6d0103df
BLAKE2b-256 218b1828db23c37237361fec38c24c4d19667cce4f5791d608cba2feb3b9d0fd

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page