AI builder guides
Bird's API surface is agent-shaped — one operation per tool, JSON in and out, machine-checkable outcomes — but a reliable agent still needs the right patterns around it. The five below cover the failure modes that actually break agent integrations: treating acceptance as delivery, retrying blind, and parsing prose instead of structure. Each pattern works the same whether your agent drives the MCP server or the bird CLI.
Pattern 1: Loop one operation at a time
Bird's tools are deliberately granular — send a message, get a message, list domains, create a webhook endpoint — and every one returns structured JSON whose fields the next step can check. Build the loop so each step's exit condition is read from the previous step's output, never inferred:
Ejemplo de código
loop:
result = run_tool(next_operation) # one operation per call
if result.ok: advance using result.data # e.g. the em_… id, the verified domain
else: branch on the failure category # see Pattern 4With the CLI, the failure category is the exit code (2 invalid input, 3 not found, 4 auth, 1 other), so the branch needs no message parsing:
Ejemplo de código
bird email get "$id" --format json > msg.json
case $? in
0) jq .status msg.json ;; # advance
3) echo "wrong id — fix, don't retry" ;;
4) bird auth login ;; # recover, then re-run
esacThe granularity is the point: an agent that can check state between steps recovers from any single failure; an agent driving one mega-operation can only start over.
Pattern 2: A send returns 202 — the outcome arrives later
POST a send and you get 202 Accepted with a message ID. Accepted means Bird took the message, not that it was delivered — the final outcome never arrives inline. It arrives as webhook events: email.delivered when the recipient's server accepts it, email.bounced when delivery fails permanently, email.complained, and so on.
An agent that declares success at 202 will silently miss every bounce. Structure the task as send-then-await instead:
Ejemplo de código
send → 202 + em_… id # record the id, do NOT report "sent successfully"
await webhook event where data.email_id == em_… id:
email.delivered → done
email.bounced → report failure with bounce_type / bounce_descriptionCorrelate on email_id — webhook payloads carry the identity fields, not your tags or metadata, so fetch the message by email_id when you need your own context back, and remember deliveries are at-least-once and unordered — deduplicate on the webhook-id header and sort by the payload timestamp. If your agent has no webhook receiver, poll the message with GET (or bird email get) until its status resolves — slower, but the same rule applies: the read-back is the source of truth, the 202 is not.
Pattern 3: Use the sandbox as your test harness
While developing the loop, don't send real mail — send to the mail sandbox's magic addresses on messagebird.dev. The outcome is determined by the address (delivered@ always delivers, bounce@ always hard-bounces, complaint@ always complains), but everything else is the real production pipeline: the same 202, the same event sequence, the same signed webhook deliveries, with no flag marking it as a test.
Ejemplo de código
for address in [delivered@, bounce@, suppressed@] @messagebird.dev:
send to address+run42@… # +label correlates the test case
assert the expected terminal event arrives (delivered / bounced / rejected)That makes the sandbox the ideal agent test harness: deterministic outcomes to assert against, zero reputation risk, no suppression-list writes, and reusable addresses across every run. An agent that passes the sandbox matrix has exercised its entire Pattern 2 path — send, await, branch — before touching a real inbox.
Pattern 4: Recover against the standard error envelope
Every Bird API error has the same shape, so error recovery is one code path, not one per endpoint:
Ejemplo de código
{
"error": {
"type": "invalid_request",
"code": "E01002",
"name": "InvalidRequest",
"message": "from address is not on a verified sending domain",
"doc_url": "https://bird.com/docs/guides/errors#E01002",
"request_id": "req_01krdgeqcxet5s7t44vh8rt9mg"
}
}Each field has a job in the loop: branch on type/code (stable, machine-readable), show message to the human, fetch doc_url when the agent needs the docs page for that exact error — it resolves to Markdown the agent can read — and log request_id so a human can hand it to Bird support. The decisive split is retryable versus not:
Ejemplo de código
4xx (except 429) → a request bug: fix the input, never retry as-is
429 → back off, then retry (Pattern 5)
5xx / timeout → retry with the same Idempotency-Key (Pattern 5)The full code catalog lives on the errors page. With the CLI, the envelope arrives on stderr and the exit code pre-classifies it (2/3/4/1 — see Pattern 1), so a shell-driving agent can branch before parsing anything.
Pattern 5: Retry safely — Idempotency-Key, and Retry-After on 429
Retries are where agents do damage: a timeout on a send, a blind retry, and the customer gets two emails. Bird's idempotency support makes the retry safe — generate one Idempotency-Key per logical operation (not per attempt) and reuse it on every retry:
Ejemplo de código
key = uuid() # once per logical send
attempt with Idempotency-Key: key
on 5xx / timeout: backoff, retry with SAME key
on 2xx with Idempotency-Replay: true → the first attempt had succeeded; do not treat as a new sendThe Idempotency-Replay: true response header marks a replay of the original response, so your agent can log "recovered" instead of "sent twice". The Bird SDKs inject a key automatically on every mutating request, so SDK-based agents get this for free; with the CLI, pass --idempotency-key on mutations that might be retried.
Rate limiting gets the opposite treatment: a 429 means slow down, not try harder. The response carries a Retry-After header — honor it as the floor of your backoff rather than inventing your own schedule:
Ejemplo de código
on 429: sleep max(Retry-After, backoff(attempt)); retry with the same keyNever retry other 4xx responses unchanged — they're cached and replayed by idempotency precisely because the same request will always produce the same error. Fix the request (Pattern 4) and use a new key, since reusing a key with a different body returns 409 IdempotencyKeyReuse.
Go deeper
- MCP server — the tool surface these patterns drive, hosted at mcp.platform.bird.com or run locally with the CLI
- CLI for agents — the same operations for shell-capable agents
- Webhooks & events — delivery semantics, signatures, and the event catalog behind Pattern 2
- Idempotency — replay semantics and failure modes behind Pattern 5
- Errors — the envelope and the full error-code catalog
- Mail sandbox — the magic-address matrix behind Pattern 3