Skip to content

Repository files navigation

Bird Python SDK

The official Python SDK for the Bird API: email, SMS, WhatsApp, verification, and Realtime, over one typed client.

📚 Documentation:https://bird.com/docs/sdks/python

Status: in development. The PyPI distribution name is messagebird-sdk; the import package is bird.

Requires Python 3.10+.

Install

pip install messagebird-sdk # or: uv add messagebird-sdk

This SDK is generated from Bird's public OpenAPI bundle inside Bird's internal monorepo, which is the single source of truth; this repository tracks tagged releases. Generation runs in the monorepo, so make generate won't work from a clone here — see CONTRIBUTING.md.

Quickstart

frombirdimportAPIError, BirdwithBird() asclient:
try:
message=client.email.send(
from_={"email": "onboarding@messagebird.dev", "name": "Bird"},
to=["delivered@messagebird.dev"],
subject="Hello from Bird",
html="<p>My first Bird email.</p>",
)
print(message.id, message.status)
exceptAPIErroraserr:
print("send failed:", err)

api_key and base_url fall back to the BIRD_API_KEY / BIRD_BASE_URL environment variables, so Bird() with no arguments works when they are set. Use the client as a context manager (with Bird(...) as client:) to close the underlying HTTP connection pool.

Email

# Sendmessage=client.email.send(from_="hi@acme.com", to=["c@x.com"], subject="Hi", text="hello")
# Fetchmessage=client.email.get("em_01krd…")
# List — iterating the page auto-paginates across cursorsformessageinclient.email.list(status="delivered"):
print(message.id, message.status)

Client-wide email defaults

Defaults fill any unset send field; a per-send value always wins.

client=Bird(
api_key="bk_eu1_...",
email_defaults={"from_": "noreply@acme.com", "reply_to": ["support@acme.com"]},
)
client.email.send(to=["c@x.com"], subject="Receipt", text="…") # uses noreply@acme.com

WhatsApp

Templates are currently the only supported content type, so every send must include one; Bird selects the sender number from the template's category.

# Sendmessage=client.whatsapp.send(
to="+31612345678",
template="bird_otp",
language="en",
components=[{"type": "body", "parameters": [{"type": "text", "text": "123456"}]}],
)
# Fetchmessage=client.whatsapp.get("wam_01krd…")
# List — iterating the page auto-paginates across cursorsformessageinclient.whatsapp.list(status=["delivered"]):
print(message.id, message.status)

Realtime

Every Realtime call is scoped to one Realtime app and authenticated with that app's own key and secret — separate from your Bird API key — configured once on the client. Calling a Realtime method without them raises BirdError before any request is sent.

client=Bird(api_key="bk_eu1_...", realtime_key="rk_...", realtime_secret="rs_...")
# Publish one event to up to 100 channelsclient.realtime.publish(
"rap_01krd…",
event="order-updated",
channels=["orders", "orders-42"],
data={"id": 42, "status": "shipped"},
exclude_connection_id="81721.1907241", # don't echo back to the client that acted
)
# Publish up to 10 events at once — each targets a single channelclient.realtime.publish_batch(
"rap_01krd…",
events=[
{"event": "order-created", "channel": "orders", "data": {"id": 1}},
{"event": "order-updated", "channel": "orders", "data": {"id": 2}},
],
)
# Live channel state — a snapshot, not a paginated collectionforchannelinclient.realtime.channels.list("rap_01krd…", prefix="presence-").data:
print(channel.name)
channel=client.realtime.channels.get("rap_01krd…", "presence-lobby", include=["member_count"])
members=client.realtime.channels.members("rap_01krd…", "presence-lobby")
# Close every connection authenticated as this memberclient.realtime.members.disconnect("rap_01krd…", "member:42")

Webhooks

frombirdimportBird, WebhookVerificationErrorclient=Bird(api_key="bk_eu1_...", webhook_secret="whsec_...")
# In your web handler — pass the RAW request body (bytes) and the request headerstry:
event=client.webhooks.unwrap(request.body, request.headers)
exceptWebhookVerificationError:
returnResponse(status=400)
ifevent.root.type=="email.delivered":
print("delivered:", event.root.data.message_id)

Endpoint management (registering/listing webhook endpoints) is not in this release; it returns once the delivery substrate stabilises.

Errors

Every failure raises a typed exception rooted at BirdError. APIError covers anything that goes wrong issuing a request — including transport failures — so a single except APIError is enough; APIStatusError carries the HTTP status_code.

frombirdimportAPIStatusError, RateLimitError, ValidationErrortry:
client.email.send(
from_={"email": "onboarding@messagebird.dev", "name": "Bird"},
to=["delivered@messagebird.dev"],
subject="Hello from Bird",
text="My first Bird email.",
)
exceptRateLimitErroraserr:
print("rate limited; retry after", err.retry_after)
exceptValidationErroraserr:
print(err.status_code, err.details)
exceptAPIStatusErroraserr:
print(err.status_code, err.code, err.request_id)

Transient failures (timeouts, 429, 5xx) retry automatically with jittered backoff that honors Retry-After; a mutation reuses one idempotency key across attempts, so a retried write never double-applies.

Raw response

Reach the status, headers, and request_id alongside the parsed model:

raw=client.email.with_raw_response.send(from_="hi@acme.com", to=["c@x.com"], subject="Hi", text="…")
print(raw.status_code, raw.request_id)
message=raw.parse()

Async

AsyncBird mirrors Bird method-for-method — await each call and async for over a list:

importasynciofrombirdimportAsyncBirdasyncdefmain() ->None:
asyncwithAsyncBird(api_key="bk_eu1_...") asclient:
awaitclient.email.send(from_="hi@acme.com", to=["c@x.com"], subject="Hi", text="hello")
asyncformessageinclient.email.list(status="delivered"):
print(message.id)
asyncio.run(main())

Configuration

OptionDescription
api_keyAPI key; falls back to BIRD_API_KEY.
region / base_urlRegion (or explicit base URL); falls back to the key prefix / BIRD_BASE_URL.
timeout, max_retriesRequest timeout and retry budget; overridable per call via options.
webhook_secretSigning secret for webhooks.unwrap.
realtime_key / realtime_secretRealtime app credentials, sent as X-Realtime-Key / X-Realtime-Secret on every client.realtime call.
email_defaultsClient-wide send defaults.
http_clientInject your own httpx.Client / AsyncClient.

client.with_options(...) derives a new client (reusing the connection pool); every method also takes a trailing options for per-call timeout / max_retries / idempotency_key / extra_headers.

Escape hatch

Any endpoint outside the typed surface is reachable through the verb methods, with the same auth, retries, and idempotency:

frombirdimportEmailMessagemessage=client.get("/v1/email/messages/em_01krd...", cast_to=EmailMessage)
client.post("/v1/some/new/endpoint", body={"key": "value"})

Design

The wire models are generated from the OpenAPI spec into bird._generated; this package is the hand-written idiomatic layer on top.

About

No description, website, or topics provided.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages