# TypeScript SDK

`@messagebird/sdk` is the official TypeScript SDK for the Bird API — fully typed, ESM-only, and edge-ready. It runs on Node.js 20.3+ and modern edge runtimes (Cloudflare Workers, Vercel Edge, Deno) using only web-standard APIs (`fetch`, `AbortSignal`, Web Crypto). This page covers the client itself; for sending email with the SDK start with the [TypeScript email quickstart](/docs/get-started/quickstarts/typescript/email).

## Install

```bash
npm install @messagebird/sdk
# pnpm add @messagebird/sdk
# yarn add @messagebird/sdk
# bun add @messagebird/sdk
```

## Construct a client

<!-- bird:snippet client.options -->

```typescript
const bird = new BirdClient({
  apiKey: process.env.BIRD_API_KEY!,
  region: "eu1", // optional — override the region from the key prefix
  baseUrl: "http://localhost:8080", // optional — overrides region entirely (local/self-hosted)
  timeout: 60_000, // per-attempt timeout in ms (default 60_000)
  maxRetries: 2, // retry budget for transient failures (default 2)
});
```

Only `apiKey` is required. The region is inferred from the key's `bk_{region}_` prefix (a `bk_eu1_…` key routes to `https://eu1.platform.bird.com`), so most clients are constructed with the key alone — see [region inference](/docs/sdks/concepts#region-inference) for the resolution rules. You can also set channel defaults at construction (for example `email: { from: "hello@acme.com" }` makes `from` optional on every send) and the webhook signing secret via `webhooks: { secret }`.

## First call

<!-- bird:snippet email.send -->

```typescript
const msg = await bird.email.send({
  from: { email: "onboarding@messagebird.dev", name: "Bird" },
  to: ["delivered@messagebird.dev"],
  subject: "Hello from Bird",
  html: "<p>My first Bird email.</p>",
});
console.log(msg.id, msg.status); // "em_…", "accepted"
```

`await` resolves to the API result directly — here an email message with its `em_*` id. For a runnable end-to-end walkthrough, follow the [TypeScript quickstart](/docs/get-started/quickstarts/typescript/email).

## Two-layer design

The SDK is a generated layer plus a hand-owned one. Wire types and the low-level HTTP plumbing are generated from Bird's OpenAPI spec, so request and response shapes are always contract-accurate; the surface you actually call — `bird.email.send(...)`, the retry loop, idempotency, pagination, errors — is hand-written for ergonomics on top. Wire fields pass through verbatim in `snake_case` (`category`, `created_at`); only SDK-defined identifiers (method names, option keys like `idempotencyKey`) are `camelCase`. The same architecture is shared by the [Go](/docs/sdks/go) and [Python](/docs/sdks/python) SDKs — see [SDK concepts](/docs/sdks/concepts).

## Automatic idempotency and retries

Every mutation (POST, PATCH, DELETE) gets an auto-generated `Idempotency-Key` header, and that one key is reused across every retry attempt — so a retried send can never deliver twice. Pass `{ idempotencyKey: "order-1234" }` in the per-call options to control the key yourself.

Retries are on by default (`maxRetries: 2`). The client retries network failures, per-attempt timeouts, and transient statuses (408, 429, 500, 502, 503, 504) with jittered exponential backoff, honoring the server's `Retry-After` header when present. Deterministic failures (4xx like 401, 404, 422) are never retried. Set `maxRetries: 0` to disable, or override per call. The full lifecycle is described in [SDK concepts](/docs/sdks/concepts#safe-retries).

## Errors

Methods throw on failure with a typed hierarchy you narrow with `instanceof`. `BirdError` is the root; `BirdAPIError` covers every error response from the server, with one subclass per error `type` — including `BirdAuthError` (401), `BirdRateLimitError` (429, with `retryAfter`), `BirdValidationError` (422, with per-field `details`), and `BirdPayloadTooLargeError` (413). Transport failures with no HTTP response are siblings of `BirdAPIError`: `BirdConnectionError` and `BirdTimeoutError`.

<!-- bird:snippet email.errors -->

```typescript
import { BirdRateLimitError, BirdValidationError, BirdAPIError } from "@messagebird/sdk";

try {
  await bird.email.send({
    from: { email: "onboarding@messagebird.dev", name: "Bird" },
    to: ["delivered@messagebird.dev"],
    subject: "Hello from Bird",
    html: "<p>My first Bird email.</p>",
  });
} catch (err) {
  if (err instanceof BirdRateLimitError) console.log(`rate limited — retry in ${err.retryAfter}s`);
  else if (err instanceof BirdValidationError) console.error(err.details);
  else if (err instanceof BirdAPIError) console.error(err.code, err.requestId);
  else throw err;
}
```

Every `BirdAPIError` carries `statusCode`, `type`, `code` (the stable `E#####` error code), `requestId`, and `docUrl`. Branch on the class (or the coarse `type`) for control flow; use `code` when you need to match one specific failure. Prefer branching on a value instead of catching? Every call also has `.safe()`:

<!-- bird:snippet email.safe -->

```typescript
const { data, error } = await bird.email
  .send({
    from: { email: "onboarding@messagebird.dev", name: "Bird" },
    to: ["delivered@messagebird.dev"],
    subject: "Hello from Bird",
    html: "<p>My first Bird email.</p>",
  })
  .safe();
if (error) console.error(error.message);
else console.log(data.id);
```

## Webhooks

`bird.webhooks.unwrap(rawBody, headers)` verifies an inbound delivery's [Standard Webhooks](https://www.standardwebhooks.com/) signature and returns a typed, discriminated event. Always pass the raw request body — the signature is computed over the raw bytes, so parsing and re-serializing first is the classic webhook bug. A bad signature, stale timestamp, or malformed headers throws `BirdWebhookVerificationError`. See [webhook verification](/docs/sdks/concepts#webhook-verification) for the cross-SDK contract and [Webhooks](/docs/guides/webhooks) for the platform side.

## Next steps

- [Email quickstart](/docs/get-started/quickstarts/typescript/email) — `send`, `get`, `list`, channel defaults, and the response shapes.
- [SDK concepts](/docs/sdks/concepts) — idempotency, retries, pagination, regions, and webhooks across all Bird SDKs.
- [API reference](/docs/api) — the underlying HTTP API; `bird.request<T>()` reaches any endpoint the typed surface doesn't cover yet, with the same auth, retries, and idempotency.