Python SDK
Official Python SDK for Posthawk. Built on httpx with typed dataclass responses and a simple result pattern. Works with Python 3.8+.
pip install posthawkInstall the SDK from PyPI. The only runtime dependency is httpx for HTTP communication.
Example
pip install posthawkPosthawk(api_key)PosthawkInitialize the client and send your first email. Pass your API key as the first argument. For self-hosted instances, pass base_url as a keyword argument.
Example
from posthawk import Posthawk
# Cloud (default base URL: https://api.posthawk.dev)
client = Posthawk("ck_live_...")
# Self-hosted
client = Posthawk("ck_live_...", base_url="https://api.yourdomain.com")
result = client.emails.send(
from_email="hello@yourdomain.com",
to="user@example.com",
subject="Welcome!",
html="<h1>Hello!</h1>",
)
if result.error:
print(result.error.message)
else:
print("Sent!", result.data.job_id)client.emails.send(**params)PosthawkResponseSend an email immediately, or schedule it for later by including scheduled_for. Returns PosthawkResponse[SendEmailResponse]. At least one of html, text, or template_id is required. Note: use from_email instead of from (which is a Python reserved word).
| Parameter | Type | Description |
|---|---|---|
from_emailrequired | str | Sender email address (maps to "from" in the API) |
torequired | str | list[str] | Recipient email address(es) |
cc | str | list[str] | CC recipient(s) |
bcc | str | list[str] | BCC recipient(s) |
subjectrequired | str | Email subject line |
html | str | HTML email body |
text | str | Plain text email body |
template_id | str | Posthawk template ID |
variables | dict[str, str] | Template variable substitution |
headers | dict[str, str] | Custom email headers (e.g. List-Unsubscribe) |
scheduled_for | str | datetime | Schedule for later (ISO 8601 string or datetime object) |
timezone | str | IANA timezone for scheduled time |
metadata | dict[str, Any] | Custom metadata attached to the email |
tags | dict[str, Any] | Custom tags for filtering and search |
reply_to | str | Reply-to email address |
Example
result = client.emails.send(
from_email="hello@yourdomain.com",
to=["user@example.com", "other@example.com"],
subject="Welcome to Acme",
html="<h1>Welcome!</h1><p>Thanks for signing up.</p>",
metadata={"user_id": "usr_123"},
tags={"campaign": "onboarding"},
)
# Schedule for later
from datetime import datetime, timedelta, timezone
result = client.emails.send(
from_email="hello@yourdomain.com",
to="user@example.com",
subject="Reminder",
text="Don't forget your meeting!",
scheduled_for=datetime.now(timezone.utc) + timedelta(hours=24),
)client.emails.get(job_id)PosthawkResponseGet the delivery status of a previously queued email by its job ID.
| Parameter | Type | Description |
|---|---|---|
job_idrequired | str | Job ID returned from the send method |
Example
result = client.emails.get("abc-123-def")
if result.data:
print(result.data.status) # pending | processing | completed | failed
print(result.data.result) # EmailJobResult(message_id=..., email_log_id=...)client.scheduled.list(**params)PosthawkResponseList scheduled emails with optional filtering by status and pagination.
| Parameter | Type | Description |
|---|---|---|
status | str | Filter by status: scheduled, sent, cancelled, failed |
limit | int | Pagination limit |
offset | int | Pagination offset |
Example
result = client.scheduled.list(
status="scheduled",
limit=10,
)
# result.data.data -> list[ScheduledEmail]
# result.data.total -> intclient.scheduled.get(id)PosthawkResponseGet the details of a specific scheduled email by its ID.
| Parameter | Type | Description |
|---|---|---|
idrequired | str | Scheduled email UUID |
Example
result = client.scheduled.get("scheduled-uuid")
if result.data:
print(result.data.data.scheduled_for) # ISO 8601 datetime
print(result.data.data.status) # scheduled | sent | cancelledclient.scheduled.cancel(id)PosthawkResponseCancel a scheduled email before it sends. Only works for emails that have not yet been processed.
| Parameter | Type | Description |
|---|---|---|
idrequired | str | Scheduled email UUID |
Example
result = client.scheduled.cancel("scheduled-uuid")
# result.data.message -> "Scheduled email cancelled successfully"client.scheduled.reschedule(id, scheduled_for=...)PosthawkResponseChange the send time of a scheduled email. Accepts a datetime object or ISO 8601 string.
| Parameter | Type | Description |
|---|---|---|
idrequired | str | Scheduled email UUID |
scheduled_forrequired | str | datetime | New send time (ISO 8601 or datetime object) |
Example
client.scheduled.reschedule(
"scheduled-uuid",
scheduled_for="2026-04-01T10:00:00Z",
)result.error / result.dataSDK methods never raise exceptions for API errors. Every call returns a PosthawkResponse with .data and .error attributes. Check .error first, then access .data safely. The only case where the SDK raises is a missing API key in the constructor.
Example
result = client.emails.send(
from_email="hello@yourdomain.com",
to="user@example.com",
subject="Test",
html="<p>Hello</p>",
)
if result.error:
print(f"Error {result.error.status_code}: {result.error.message}")
return
# result.data is guaranteed non-None here
print(result.data.job_id)client.domains.*PosthawkResponseManage sending domains programmatically — add, verify, enable inbound receiving, configure webhooks, and set sending limits. Ideal for SaaS platforms that let their users add custom sending domains.
Example
# Add a domain
result = client.domains.create(
domain="mail.customer.com",
region="us-east-1",
)
# result.data.dns_records → DNS records to configure
# List all domains
result = client.domains.list()
# Trigger verification check
result = client.domains.verify("domain-uuid")
# result.data.sending_enabled → True when verified
# Enable inbound receiving
client.domains.enable_receiving("domain-uuid")
# Set a webhook for inbound emails
client.domains.update_webhook(
"domain-uuid",
webhook_url="https://yourapp.com/api/incoming-email",
)
# Test the webhook
client.domains.test_webhook("domain-uuid")
# Set per-domain sending limits
client.domains.update_limits(
"domain-uuid",
max_daily_emails=1000,
max_monthly_emails=25000,
)
# Remove limits (None = unlimited)
client.domains.update_limits(
"domain-uuid",
max_daily_emails=None,
max_monthly_emails=None,
)
# Delete a domain
client.domains.delete("domain-uuid")with Posthawk(...) as client:Use a context manager to automatically close the underlying httpx connection pool when done. This ensures clean resource cleanup.
Example
from posthawk import Posthawk
with Posthawk("ck_live_...") as client:
result = client.emails.send(
from_email="hello@yourdomain.com",
to="user@example.com",
subject="Hello",
html="<h1>Hi!</h1>",
)
print(result.data.job_id)
# Connection pool is automatically closed here