Files
GibSend/packages/python-sdk/README.md
T
KM Koushik 09bdb8aaad feat(python-sdk): add webhook verification and event handling (#344)
* feat(python-sdk): add webhook verification and event handling

Add webhook support to the Python SDK matching the JS SDK implementation:
- Add Webhooks class with verify() and construct_event() methods
- Implement HMAC-SHA256 signature verification with timing-safe comparison
- Add timestamp validation with configurable tolerance (default 5 minutes)
- Add comprehensive webhook event types (18 events: email, contact, domain, test)
- Add WebhookVerificationError with typed error codes
- Export webhook constants (headers) and types

* fix(python-sdk): harden webhook parsing and typing

Normalize invalid UTF-8 webhook payloads to INVALID_BODY errors so verify() safely returns false, and narrow base email webhook event types to avoid discriminated-union overlap. Add regression tests for both paths.

* chore(python-sdk): bump package version to 0.2.9

* feat(python-sdk): add local webhook test example project

Add a runnable Flask receiver and signed webhook sender under packages/python-sdk/example, and link it from the Python SDK README for local verification.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-08 08:18:14 +11:00

3.2 KiB

UseSend Python SDK

A minimal Python SDK for the UseSend API, mirroring the structure of the JavaScript SDK.

Installation

Install via pip or Poetry:

pip install usesend
# or
poetry add usesend

Usage

from usesend import UseSend, types

# By default: raises UseSendHTTPError on non-2xx.
client = UseSend("us_123")

# 1) TypedDict payload (autocomplete in IDEs). Use dict to pass 'from'.
payload: types.EmailCreate = {
    "to": "test@example.com",
    "from": "no-reply@example.com",
    "subject": "Hello",
    "html": "<strong>Hi!</strong>",
}
resp, _ = client.emails.send(payload=payload)

# 2) Or pass a plain dict (supports 'from')
resp, _ = client.emails.send(payload={
    "to": "test@example.com",
    "from": "no-reply@example.com",
    "subject": "Hello",
    "html": "<strong>Hi!</strong>",
})

# Idempotent retries: same payload + same key returns the original response
resp, _ = client.emails.send(
    payload=payload,
    options={"idempotency_key": "signup-123"},
)

# Works for batch requests as well
resp, _ = client.emails.batch(
    payload=[payload],
    options={"idempotency_key": "bulk-welcome-1"},
)
# If the same key is reused with a different payload, the API responds with HTTP 409.

# 3) Campaigns
campaign_payload: types.CampaignCreate = {
    "name": "Welcome Series",
    "subject": "Welcome to our service!",
    "html": "<p>Thanks for joining us!</p>",
    "from": "welcome@example.com",
    "contactBookId": "cb_1234567890",
}
campaign_resp, _ = client.campaigns.create(payload=campaign_payload)

# Schedule a campaign
schedule_payload: types.CampaignSchedule = {
    "scheduledAt": "2024-12-01T10:00:00Z",
}
schedule_resp, _ = client.campaigns.schedule(
    campaign_id=campaign_resp["id"],
    payload=schedule_payload
)

# Pause/resume campaigns
client.campaigns.pause(campaign_id="campaign_123")
client.campaigns.resume(campaign_id="campaign_123")

# Toggle behavior if desired:
# - raise_on_error=False: return (None, error_dict) instead of raising
# No model parsing occurs; methods return plain dicts following the typed shapes.
client = UseSend("us_123", raise_on_error=False)
raw, err = client.emails.get(email_id="email_123")
if err:
    print("error:", err)
else:
    print("ok:", raw)

Webhook Local Example

For a runnable webhook verification demo project, see:

  • example/webhook-test-project/README.md

Development

This package is managed with Poetry. Models are maintained in-repo under usesend/types.py (readable names). Update this file as the API evolves.

It is published as usesend on PyPI.

Available Resources

  • Emails: client.emails.send(), client.emails.get()
  • Contacts: client.contacts.create(), client.contacts.get(), client.contacts.list()
  • Domains: client.domains.create(), client.domains.get(), client.domains.verify()
  • Campaigns: client.campaigns.create(), client.campaigns.get(), client.campaigns.schedule(), client.campaigns.pause(), client.campaigns.resume()

Notes

  • Human-friendly models are available under usesend.types (e.g., EmailCreate, CampaignCreate, Contact, APIError).
  • Endpoint methods accept TypedDict payloads or plain dicts via the payload= keyword.