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 --
MaielrandAsyncMailer - Full type hints (PEP 484) with
py.typedmarker - 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-Afterheader) - 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 identificationX-SDK-Timestamp: <ISO 8601>-- Request timestampX-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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f237bff6e243be0a7d479f3b9be97ec72c79e474c378b28b5d9b2a58cfec649e
|
|
| MD5 |
d8cc7f9e1306ca7891608125c4425df0
|
|
| BLAKE2b-256 |
a3ae67370cc38699c90e41e9d4ff66d52cc498f2592e426252a17715e9752473
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
98a69f33a4c2bece7635c156aa2a4ae99bcc002e807011a50b52141a262f5863
|
|
| MD5 |
6d3232705895454868ea491a6d0103df
|
|
| BLAKE2b-256 |
218b1828db23c37237361fec38c24c4d19667cce4f5791d608cba2feb3b9d0fd
|