Skip to main content

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 because from is a Python reserved word. The SDK maps it to from in 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

senddy-0.2.1.tar.gz (16.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

senddy-0.2.1-py3-none-any.whl (20.1 kB view details)

Uploaded Python 3

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

Hashes for senddy-0.2.1.tar.gz
Algorithm Hash digest
SHA256 da948128a23d871c984e238aefdce6cfdf641fc996efb2aa7193fb94646eeb4d
MD5 e84d389b7d8a1a60e23de80eb1932193
BLAKE2b-256 ed96e4310138b25c7e47f6446e734b84433a51a3964c0ff34d972f7f57e399ee

See more details on using hashes here.

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

Hashes for senddy-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 ea1c32030579133d198f04a24b4cfd17aef9c9a5fb2581892f0826f59c3eaeef
MD5 6857656cd1ad7d61a0adc64173d9c2c1
BLAKE2b-256 b18c3af7965bd23a3fc341d466b6cfa4686a390f0698a93d76eadfc6cc9fd770

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page