Skip to main content

Bird Python SDK

The official Python SDK for the Bird email platform.

📚 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

from bird import APIError, Bird

with Bird() as client:
    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)
    except APIError as err:
        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

# Send
message = client.email.send(from_="hi@acme.com", to=["c@x.com"], subject="Hi", text="hello")

# Fetch
message = client.email.get("em_01krd…")

# List — iterating the page auto-paginates across cursors
for message in client.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.

# Send
message = client.whatsapp.send(
    to="+31612345678",
    template="bird_otp",
    language="en",
    components=[{"type": "body", "parameters": [{"type": "text", "text": "123456"}]}],
)

# Fetch
message = client.whatsapp.get("wam_01krd…")

# List — iterating the page auto-paginates across cursors
for message in client.whatsapp.list(status=["delivered"]):
    print(message.id, message.status)

# List the templates available to the workspace
for template in client.whatsapp_templates.list().data:
    print(template.name, template.status)

Webhooks

from bird import Bird, WebhookVerificationError

client = Bird(api_key="bk_eu1_...", webhook_secret="whsec_...")

# In your web handler — pass the RAW request body (bytes) and the request headers
try:
    event = client.webhooks.unwrap(request.body, request.headers)
except WebhookVerificationError:
    return Response(status=400)

if event.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.

from bird import APIStatusError, RateLimitError, ValidationError

try:
    client.email.send(
        from_={"email": "onboarding@messagebird.dev", "name": "Bird"},
        to=["delivered@messagebird.dev"],
        subject="Hello from Bird",
        text="My first Bird email.",
    )
except RateLimitError as err:
    print("rate limited; retry after", err.retry_after)
except ValidationError as err:
    print(err.status_code, err.details)
except APIStatusError as err:
    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:

import asyncio
from bird import AsyncBird

async def main() -> None:
    async with AsyncBird(api_key="bk_eu1_...") as client:
        await client.email.send(from_="hi@acme.com", to=["c@x.com"], subject="Hi", text="hello")
        async for message in client.email.list(status="delivered"):
            print(message.id)

asyncio.run(main())

Configuration

Option Description
api_key API key; falls back to BIRD_API_KEY.
region / base_url Region (or explicit base URL); falls back to the key prefix / BIRD_BASE_URL.
timeout, max_retries Request timeout and retry budget; overridable per call via options.
webhook_secret Signing secret for webhooks.unwrap.
email_defaults Client-wide send defaults.
http_client Inject 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:

from bird import EmailMessage

message = 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.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

messagebird_sdk-0.7.2.tar.gz (55.9 kB view details)

Uploaded Source

Built Distribution

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

messagebird_sdk-0.7.2-py3-none-any.whl (69.2 kB view details)

Uploaded Python 3

File details

Details for the file messagebird_sdk-0.7.2.tar.gz.

File metadata

  • Download URL: messagebird_sdk-0.7.2.tar.gz
  • Upload date:
  • Size: 55.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for messagebird_sdk-0.7.2.tar.gz
Algorithm Hash digest
SHA256 8bfe6f320dbc6d5ce5607d934d523e293320def026ba3c6efbd9cb577567509f
MD5 d4abd0a6b3030066d18e0cff0d65eea4
BLAKE2b-256 8578172c12299955eded38b465bbdb1f70006ed25a96e147edb7270b7f5190af

See more details on using hashes here.

Provenance

The following attestation bundles were made for messagebird_sdk-0.7.2.tar.gz:

Publisher: release.yaml on messagebird/bird-sdk-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file messagebird_sdk-0.7.2-py3-none-any.whl.

File metadata

  • Download URL: messagebird_sdk-0.7.2-py3-none-any.whl
  • Upload date:
  • Size: 69.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for messagebird_sdk-0.7.2-py3-none-any.whl
Algorithm Hash digest
SHA256 c18dbac7116d2ff0c3bdcceeadf59ee4e1e174ce715aac764fbac37934c0b545
MD5 aefd009458c89da1329544da37de8aef
BLAKE2b-256 65e7e2f691ff71fdf9f9f705122bc5a16747bdc1dec6e07e12b3877c25c94ed8

See more details on using hashes here.

Provenance

The following attestation bundles were made for messagebird_sdk-0.7.2-py3-none-any.whl:

Publisher: release.yaml on messagebird/bird-sdk-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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