PHP SDK
messagebird/sdk is the official PHP SDK for the Bird API: a typed, hand-written surface over a generated wire layer. It targets PHP 8.2+ and is synchronous; HTTP goes through any PSR-18 client you already have (Guzzle, Symfony HttpClient, …), discovered automatically. This page covers the client itself; to send email end to end, start with the PHP email quickstart.
Install
Exemplo de código
composer require messagebird/sdkConstruct a client
Exemplo de código
$bird = new Bird(
getenv('BIRD_API_KEY') ?: '',
region: 'eu1', // optional — overrides the region inferred from the key prefix
maxRetries: 2, // retry budget for transient failures (default 2)
email: new EmailDefaults(from: 'hello@acme.com'), // optional channel defaults, e.g. a default `from`
webhookSecret: getenv('BIRD_WEBHOOK_SECRET') ?: null, // signing secret for $bird->webhooks->unwrap()
);Only the API key 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 for the rules. baseUrl overrides the region entirely (local or self-hosted). Channel defaults set here (for example email: new EmailDefaults(from: "hello@acme.com")) make that field optional on every send, and webhookSecret is the secret $bird->webhooks->unwrap() verifies with. A per-request timeout is configured on the PSR-18 client you pass, not on Bird. PSR-18 has no portable timeout, so the transport owns it.
First call
Exemplo de código
$message = $bird->email->send(
from: 'Bird <onboarding@messagebird.dev>',
to: ['delivered@messagebird.dev'],
subject: 'Hello from Bird',
html: '<p>My first Bird email.</p>',
);
echo $message->getId(), ' ', $message->getStatus();The call returns the API result directly: here an email message with its em_* id. For a runnable walkthrough, follow the PHP quickstart.
Two-layer design
The SDK is a generated layer plus a hand-owned one. The wire types under MessageBird\Wire are generated from Bird's OpenAPI spec (via jane-php), so request and response shapes are always contract-accurate; the surface you call ($bird->email->send(...), the retry loop, idempotency, pagination, errors, webhook verification) is hand-written on top. Wire fields pass through verbatim in snake_case (category, created_at); only SDK-defined identifiers (method names, option names like idempotencyKey) are camelCase. The same architecture is shared by the TypeScript, Go, and Python SDKs; see SDK 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. Retries are on by default (maxRetries: 2): the client retries transient failures (429, 5xx except 501, and PSR-18 transport errors) with jittered exponential backoff, honoring the server's Retry-After header. Deterministic failures (4xx like 401, 404, 422) are never retried. Override either per call:
Exemplo de código
$bird->email->send(
from: 'Bird <onboarding@messagebird.dev>',
to: ['delivered@messagebird.dev'],
subject: 'Hello from Bird',
html: '<p>My first Bird email.</p>',
options: new RequestOptions(idempotencyKey: 'order-1234', maxRetries: 0),
);Errors
A failed call throws. MessageBird\Exception\ApiException covers every error response from the server and carries status (the HTTP status), type (the coarse category), and errorCode (the stable E##### code). A transport failure that exhausts the retry budget throws MessageBird\Exception\ConnectionException instead: there is no HTTP response to report. Both extend MessageBird\Exception\BirdException, so catch that to handle either.
Exemplo de código
try {
$bird->email->send(
from: 'Bird <onboarding@messagebird.dev>',
to: ['delivered@messagebird.dev'],
subject: 'Hello from Bird',
html: '<p>My first Bird email.</p>',
);
} catch (ApiException $e) {
// The server returned an error response. $status is the HTTP status, $type
// the coarse category, $errorCode the stable E##### code.
echo $e->status, ' ', $e->errorCode ?? $e->type ?? 'error';
} catch (ConnectionException $e) {
// The transport failed and the retry budget was exhausted — no HTTP response.
echo 'transport error: ', $e->getMessage();
}Pagination
List methods return a lazy Core\Page; iterating it with foreach auto-paginates across cursors, fetching pages on demand:
Exemplo de código
foreach ($bird->email->list(['status' => 'delivered']) as $message) {
echo $message->getId(), "\n";
}For manual cursor control, fetch() returns a single page: its data plus the forward nextCursor, which you pass back as starting_after to advance:
Exemplo de código
$page = $bird->email->list(['status' => 'delivered'])->fetch();
foreach ($page->data as $message) {
echo $message->getId(), "\n";
}
$next = $page->nextCursor; // pass back as starting_after to fetch the next pageEscape hatch
Endpoints not yet on the typed surface are reachable through $bird->get / post / put / patch / delete, with the same auth, retries, idempotency, and base-URL handling:
Exemplo de código
// The verb methods (get/post/put/patch/delete) run through the same auth,
// retries, idempotency, and base-URL handling as the typed methods; pass a
// path and, for writes, a body array.
$messages = $bird->get('/v1/sms/messages', query: ['limit' => 10]);
$created = $bird->post('/v1/sms/messages', body: [
'to' => '+15551234567',
'text' => 'Your code is 123456.',
'category' => 'authentication',
]);Find the paths in the API reference.
Webhooks
Verify a delivered webhook's Standard Webhooks signature and get the decoded event. Set the signing secret on the client (or pass it per call), and pass the raw request body: the signature is over the raw bytes, so parsing before verifying is the classic webhook bug.
Exemplo de código
// In your web handler: pass the RAW request body — the signature is over the
// raw bytes, so parsing before verifying is the classic webhook bug.
$rawBody = file_get_contents('php://input') ?: '';
try {
$event = $bird->webhooks->unwrap($rawBody, getallheaders());
// $event is the decoded payload as an array; branch on $event['type'].
echo $event['type'];
} catch (WebhookVerificationError) {
http_response_code(400); // bad signature, stale timestamp, or missing/malformed headers
}Next steps
- PHP email quickstart: a runnable end-to-end send.
- SDK concepts: the retry, pagination, and region model shared across SDKs.
- API reference: every operation, with PHP examples.