Installation¶
API Client¶
Initialize¶
In production, prefer environment variables (SEMA_API_KEY, SEMA_BASE_URL) and omit the key (and optionally base URL) below.
from sema_sdk import SemaClient
client = SemaClient(
api_key="sk_live_...", # optional if SEMA_API_KEY is set
base_url="https://dev-api.withsema.com", # optional, falls back to SEMA_BASE_URL
timeout=30.0, # optional, in seconds
max_retries=3, # optional, set to 0 to disable retries
)
You can omit api_key when SEMA_API_KEY is set; omit base_url when SEMA_BASE_URL is set.
The client requires HTTPS in production: it rejects live keys (sk_live_...) with a non-HTTPS base URL and warns for other HTTP base URLs (e.g. localhost).
Use as a context manager for automatic cleanup:
The client automatically retries requests on transient failures (5xx errors, network errors, rate limits) with exponential backoff. Rate-limited requests (429) respect the Retry-After header when present.
Create an Inbox¶
inbox = client.create_inbox(
name="Support Inbox",
webhook_url="https://example.com/webhooks/sema",
webhook_secret="whsec_...", # optional, auto-generated if omitted
)
print(inbox.id)
Upload Content¶
# From bytes
item = client.upload_item(
inbox_id=inbox.id,
file=b"Hello, World!",
sender_address="sender@example.com",
subject="Test Upload",
filename="hello.txt",
)
# From file
with open("document.pdf", "rb") as f:
item = client.upload_item(
inbox_id=inbox.id,
file=f,
sender_address="sender@example.com",
)
print(item.id, item.status, item.is_duplicate)
Get Item Details¶
List Items¶
result = client.list_items(inbox_id="...", limit=10)
for item in result.items:
print(item.id, item.status)
List Inboxes¶
result = client.list_inboxes(limit=10, offset=0)
print(result.total)
for inbox in result.inboxes:
print(inbox.id, inbox.name)
list_inboxes(limit=..., offset=...) returns an InboxList with inboxes and total.
Pagination Iterators¶
For large result sets, use iterators to automatically paginate:
# Iterate through all items in an inbox
for item in client.iter_items(inbox_id):
print(item.id, item.status)
# Iterate through all inboxes
for inbox in client.iter_inboxes():
print(inbox.id, inbox.name)
# Custom page size
for item in client.iter_items(inbox_id, page_size=50):
print(".", end="")
Check Deliveries¶
result = client.get_item_deliveries(item_id="...")
for delivery in result.deliveries:
print(delivery.status, delivery.attempt_count)
for attempt in delivery.attempts:
print(f" Attempt {attempt.attempt_number}: {attempt.status_code}")
Async Client¶
For async applications, use AsyncSemaClient:
from sema_sdk import AsyncSemaClient
async with AsyncSemaClient(api_key="sk_live_...") as client:
inbox = await client.create_inbox(name="Support Inbox")
item = await client.get_item(item_id="...")
# Async pagination
async for item in client.iter_items(inbox_id):
print(item.id)
AsyncSemaClient has the same methods as SemaClient but all are async and require await.
Webhook Verification¶
Initialize¶
from sema_sdk import WebhookVerifier
verifier = WebhookVerifier(
secret="whsec_...",
tolerance_seconds=300, # optional, default 5 minutes
)
Verify and Parse¶
from sema_sdk import WebhookVerificationError
def handle_webhook(request):
try:
event = verifier.verify(
payload=request.body,
headers=request.headers,
)
except WebhookVerificationError as e:
return Response(status=400, body=str(e))
# Use webhook_id as idempotency key
if already_processed(event.webhook_id):
return Response(status=200)
# Process the event
print(event.payload.event_type) # "ITEM_READY"
print(event.payload.item_id)
print(event.payload.inbox_id)
print(event.payload.payload_mode) # "full" or "thin"
# Access deliverable data
print(event.payload.deliverable.raw_ref)
if event.payload.deliverable.sender:
print(event.payload.deliverable.sender.address)
mark_processed(event.webhook_id)
return Response(status=200)
Error Handling¶
from sema_sdk import (
SemaError, # Base exception
SemaAPIError, # API errors (has status_code, response_body)
AuthenticationError, # 401 - Invalid API key
NotFoundError, # 404 - Resource not found
RateLimitError, # 429 - Rate limit exceeded
WebhookVerificationError, # Signature/timestamp invalid
)
try:
item = client.get_item("nonexistent")
except NotFoundError:
print("Item not found")
except SemaAPIError as e:
print(f"API error {e.status_code}: {e.message}")
Observability Hooks¶
Add optional hooks for logging, tracing, or metrics:
def before_request(ctx):
print(f"→ {ctx['method']} {ctx['url']} (attempt {ctx['attempt']})")
def after_response(ctx):
print(f"← {ctx['status']} in {ctx['duration_ms']:.1f}ms")
def on_error(ctx, error):
print(f"✗ {ctx['method']} {ctx['url']}: {error}")
client = SemaClient(
api_key="sk_live_...",
on_before_request=before_request,
on_after_response=after_response,
on_error=on_error,
)
Hook context includes:
| Field | Description | Available In |
|---|---|---|
method |
HTTP method (GET, POST, etc.) | All hooks |
url |
Request URL | All hooks |
attempt |
Attempt number (1-based) | All hooks |
status |
HTTP status code | on_after_response |
duration_ms |
Request duration in milliseconds | on_after_response |
Hooks can be sync or async. Errors raised in hooks are silently ignored to prevent breaking SDK functionality.
Inline Images¶
Emails with pasted screenshots or inline images include cid: references in the HTML body. To render these images:
# Get attachments with presigned URLs
attachments = client.get_item_attachments(item_id)
# Build lookup from content_id to download URL
cid_to_url = {
att.content_id: att.download_url
for att in attachments.attachments
if att.content_id and att.download_url
}
# Replace cid: references in HTML body
html = event.payload.deliverable.content_summary.body_html
if html:
for cid, url in cid_to_url.items():
html = html.replace(f"cid:{cid}", url)
Type Reference¶
All responses are Pydantic models with full type hints:
Inbox- Inbox configuration and statusInboxList- List response withinboxesandtotalItem- Item details including sender, status, metadataDelivery- Delivery status and attemptsWebhookEvent- Verified webhook withwebhook_id,timestamp,payloadWebhookPayload- Parsed webhook body withdeliverabledata