Official Python SDK for Senddy.io
Project description
Senddy Python SDK
Official Python SDK for the Senddy.io email API.
Installation
Requires Python 3.9+.
pip install senddy
Quick Start
from senddy import Senddy, SendEmailParams
client = Senddy("senddy_live_api_key")
result = client.emails.send(SendEmailParams(
from_="sender@senddy.io",
to="recipient@example.com",
subject="Hello",
html="<p>Welcome!</p>",
))
print(result.id) # email_abc123
Async Support
from senddy import AsyncSenddy, SendEmailParams
async with AsyncSenddy("senddy_live_api_key") as client:
result = await client.emails.send(SendEmailParams(
from_="sender@senddy.io",
to="recipient@example.com",
subject="Hello",
html="<p>Welcome!</p>",
))
Configuration
All options are optional — sensible defaults are used if you don't override them.
client = Senddy(
"senddy_live_api_key",
timeout=30.0, # seconds
retries=2,
headers={"X-Custom": "value"}, # extra headers on every request
)
If no API key is passed to the constructor, the SDK reads from the SENDDY_API_KEY environment variable.
Both clients support context managers for proper resource cleanup:
with Senddy("senddy_live_api_key") as client:
# client.close() called automatically on exit
...
Emails
Send
from senddy import SendEmailParams, Attachment, RequestOptions
result = client.emails.send(
SendEmailParams(
from_="sender@senddy.io",
to=["alice@example.com", "bob@example.com"],
subject="Hello",
html="<p>Hi there</p>",
text="Hi there", # optional plain-text fallback
cc="cc@example.com", # optional
bcc="bcc@example.com", # optional
reply_to="reply@example.com", # optional
tags={"campaign": "welcome"}, # optional metadata
attachments=[Attachment( # optional
filename="report.pdf",
content=base64_string,
content_type="application/pdf",
)],
),
options=RequestOptions(idempotency_key="unique-key"), # prevents duplicate sends
)
Note: The
from_parameter uses a trailing underscore becausefromis a Python reserved word. The SDK maps it tofromin the API request automatically.
Get
email = client.emails.get("email_abc123")
print(email.status) # 'delivered'
print(email.recipients) # list of EmailRecipient
print(email.events) # list of EmailEvent
List
from senddy import ListEmailsParams
result = client.emails.list(ListEmailsParams(
limit=25,
offset=0,
status="delivered",
since="2026-01-01T00:00:00Z",
))
for email in result.data:
print(email.subject)
print(result.pagination.total)
Get rendered content
content = client.emails.get_content("email_abc123")
print(content.html) # str or None
print(content.text) # str or None
Download EML
download = client.emails.download("email_abc123")
# download.content is bytes containing the raw EML
# download.content_type is 'message/rfc822'
Suppressions
List
from senddy import ListSuppressionsParams
result = client.suppressions.list(ListSuppressionsParams(
limit=50,
search="example.com",
reason="hard_bounce",
))
Create
from senddy import CreateSuppressionParams
entry = client.suppressions.create(CreateSuppressionParams(
email_address="block@example.com",
))
Delete
result = client.suppressions.delete("block@example.com")
print(result.removed) # True
Domains
from senddy import CreateDomainParams
# Add a sending domain
domain = client.domains.create(CreateDomainParams(domain="mail.example.com"))
# Trigger DNS verification
client.domains.verify(domain.id)
# List, get, delete
client.domains.list()
client.domains.get(domain.id)
client.domains.delete(domain.id)
# Rotate DKIM keys
client.domains.regenerate_dkim(domain.id)
# Bulk DNS health check across all domains
client.domains.check_dns_health()
Inbound Routes
from senddy import CreateInboundRouteParams, UpdateInboundRouteParams
# Create a catchall route that forwards to your webhook
route = client.inbound_routes.create(CreateInboundRouteParams(
match_type="catchall",
webhook_url="https://your-app.example.com/inbound",
))
# List, get, update, delete
client.inbound_routes.list()
client.inbound_routes.get(route.id)
client.inbound_routes.update(route.id, UpdateInboundRouteParams(enabled=False))
client.inbound_routes.delete(route.id)
# Verify the MX record points at Senddy
client.inbound_routes.verify_mx(route.id)
See the inbound docs for match patterns, webhook signing, and full configuration.
Inbound Emails
from senddy import ListInboundEmailsParams
result = client.inbound_emails.list(ListInboundEmailsParams(route_id=42, limit=50))
email = client.inbound_emails.get("inbound_xyz")
print(email.text_body, email.attachments)
Webhook Endpoints
from senddy import CreateWebhookEndpointParams, UpdateWebhookEndpointParams
# Register an endpoint to receive event callbacks
result = client.webhook_endpoints.create(CreateWebhookEndpointParams(
url="https://your-app.example.com/webhooks/senddy",
domain="mail.example.com",
event_type="email.delivered",
))
# Save result.secret — you'll need it to verify the signature on incoming webhooks.
# Test, list, update, delete
client.webhook_endpoints.test(result.id)
client.webhook_endpoints.list()
client.webhook_endpoints.update(result.id, UpdateWebhookEndpointParams(url="https://new-url.example.com"))
client.webhook_endpoints.delete(result.id)
# Inspect delivery history
deliveries = client.webhook_endpoints.deliveries(result.id)
Billing
balance = client.billing.get_balance()
print(balance.credit_balance, balance.billing_tier)
Error Handling
from senddy import (
APIError,
ValidationError,
RateLimitError,
AuthenticationError,
)
try:
client.emails.send(params)
except ValidationError as err:
print(f"Validation: {err.details}")
except RateLimitError as err:
print(f"Rate limited. Retry after {err.retry_after}s")
except AuthenticationError as err:
print(f"Auth error: {err}")
except APIError as err:
print(f"API error {err.status_code}: {err}")
Error Types
| Class | Status | Description |
|---|---|---|
ValidationError |
400 | Invalid request, includes .details list |
AuthenticationError |
401 | Missing or invalid API key |
ForbiddenError |
403 | Insufficient permissions |
NotFoundError |
404 | Resource not found |
RateLimitError |
429 | Rate limit exceeded, includes .retry_after |
InternalError |
5xx | Server error |
All error classes inherit from APIError, which inherits from Exception.
Retries
The SDK automatically retries on 429 and 5xx responses with exponential backoff and jitter. Retries respect the Retry-After header. Non-retryable errors (4xx except 429) are raised immediately.
Requirements
- Python >= 3.9
- Runtime dependency: httpx
Type Safety
The package ships with a py.typed marker (PEP 561) and all public types are fully annotated. Works with mypy, pyright, and IDE autocompletion out of the box.
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
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 senddy-0.2.1.tar.gz.
File metadata
- Download URL: senddy-0.2.1.tar.gz
- Upload date:
- Size: 16.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
da948128a23d871c984e238aefdce6cfdf641fc996efb2aa7193fb94646eeb4d
|
|
| MD5 |
e84d389b7d8a1a60e23de80eb1932193
|
|
| BLAKE2b-256 |
ed96e4310138b25c7e47f6446e734b84433a51a3964c0ff34d972f7f57e399ee
|
File details
Details for the file senddy-0.2.1-py3-none-any.whl.
File metadata
- Download URL: senddy-0.2.1-py3-none-any.whl
- Upload date:
- Size: 20.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ea1c32030579133d198f04a24b4cfd17aef9c9a5fb2581892f0826f59c3eaeef
|
|
| MD5 |
6857656cd1ad7d61a0adc64173d9c2c1
|
|
| BLAKE2b-256 |
b18c3af7965bd23a3fc341d466b6cfa4686a390f0698a93d76eadfc6cc9fd770
|