Python SDK
The official shipmail package provides both synchronous and asynchronous clients. Requires Python 3.10+.
Installation
pip install shipmailuv add shipmailQuick start
from shipmail import ShipMail
client = ShipMail("sm_live_...")
message = client.messages.send({
"from": "hello@yourdomain.com",
"to": ["recipient@example.com"],
"subject": "Hello from shipmail",
"text": "It works.",
})
print(message["id"]) # msg_...Async client
Use AsyncShipMail for async/await workflows. Works as a context manager for automatic cleanup.
from shipmail import AsyncShipMail
async with AsyncShipMail("sm_live_...") as client:
message = await client.messages.send({
"from": "hello@yourdomain.com",
"to": ["recipient@example.com"],
"subject": "Hello",
"text": "Hi there",
})Configuration
client = ShipMail(
"sm_live_...",
base_url="https://shipmail.to/api/v1", # default
max_retries=2, # default
timeout=30.0, # seconds, default
)| Option | Default | Description |
|---|---|---|
| api_key | required | Your sm_live_... key. |
| base_url | https://shipmail.to/api/v1 | API base URL. |
| max_retries | 2 | Retry count for 429 and 5xx responses. |
| timeout | 30.0 | Request timeout in seconds. |
Resources
The client exposes each API resource as a property:
| Property | Methods |
|---|---|
| client.domains | create, list, get, update, delete, verify, search, register |
| client.mailboxes | create, list, get, update, delete, update_auto_reply |
| client.messages | send, get |
| client.threads | list, get, reply |
| client.webhooks | create, list, get, update, delete, rotate_secret, test, list_deliveries |
| client.suppressions | list, remove |
| client.status | get |
Error handling
API errors raise typed subclasses of ShipMailError. Each error exposes status, type, request_id, and retryable.
from shipmail import (
ShipMailError,
ValidationError,
RateLimitError,
APIConnectionError,
)
try:
client.messages.send({...})
except ValidationError as err:
print(err.details) # field-level errors
except RateLimitError as err:
print(err.retry_after) # seconds to wait
except ShipMailError as err:
print(err.status) # HTTP status code
print(err.request_id) # request ID for support| Class | Status |
|---|---|
| AuthenticationError | 401 |
| AuthorizationError | 403 |
| ValidationError | 422 |
| NotFoundError | 404 |
| ConflictError | 409 |
| RateLimitError | 429 |
| InternalServerError | 500 |
| APIConnectionError | Network failure |
Pagination
Manual cursor-based pagination:
page = client.domains.list({"limit": 10})
for domain in page["data"]:
print(domain["name"])
if page["pagination"]["has_more"]:
next_page = client.domains.list({
"cursor": page["pagination"]["next_cursor"],
})Auto-pagination iterates all pages automatically:
# Sync
for domain in client.domains.list_auto_paginating(limit=25):
print(domain["name"])
# Async
async for domain in client.domains.list_auto_paginating(limit=25):
print(domain["name"])Webhook verification
The SDK exports a standalone verify_webhook function. No client instance needed.
from shipmail import verify_webhook, WebhookVerificationError
try:
event = verify_webhook(raw_body, headers, webhook_secret)
print(event["event_type"]) # "message.received"
except WebhookVerificationError:
pass # invalid signatureThrows WebhookVerificationError if the signature is invalid or the timestamp is outside the 5-minute tolerance window. Pass tolerance_in_seconds=600 to customize.
Per-request options
Every method accepts an optional options argument for request-level overrides:
client.domains.create(
{"name": "example.com"},
{"idempotency_key": "550e8400-...", "timeout": 10.0},
)