BlinkInbox API
Disposable inboxes you control from code. Create an address, point your app at it, and read what arrives, with the one-time code and verification link already extracted.
Authentication
Create a key in the dashboard and send it as a bearer token. Keys start with bb_live_. X-API-Key: bb_live_… works too.
Authorization: Bearer bb_live_xxxxxxxxxxxxxxxxxxxxxxxxQuickstart
The whole email-verification test: create, trigger, wait.
# 1. Create an inbox
curl -s -X POST "https://api.blinkinbox.club/api/v1/inboxes" \
-H "Authorization: Bearer $BLINKINBOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prefix": "signup", "ttlMinutes": 30}'
# → {"id":"…","address":"signup4k2j9x1a@blinkinbox.club", …}
# 2. Trigger your signup flow with that address, then wait for the code
curl -s "https://api.blinkinbox.club/api/v1/inboxes/signup4k2j9x1a@blinkinbox.club/otp?timeout=30" \
-H "Authorization: Bearer $BLINKINBOX_API_KEY"
# → {"otp":"482913","verificationLink":null,"subject":"Your code", …}Inboxes
POST /inboxes. All fields are optional.
| Field | Type | Description |
|---|---|---|
| prefix | string | Start of the address. 8 random characters are appended: signup4k2j9x1a@blinkinbox.club |
| localPart | string | Exact local part. On custom domains, or on Pro for the shared domain. |
| domain | string | A verified custom domain. Defaults to blinkinbox.club. |
| ttlMinutes | integer | Lifetime. Max 60 (Developer), 10,080 (Pro), 43,200 (Scale). |
| label | string | Free-form note, e.g. the test name or CI run id. |
Anywhere a path takes {inbox}, you can pass the inbox id or its full address.
Waiting for mail
GET /inboxes/{inbox}/messages/wait?timeout=30 holds the request open until a matching message exists (max 60s), then returns it. You get 408 wait_timeout if nothing arrives.
from,subject: case-insensitive “contains” filters.since: ISO timestamp. Without it, mail already in the inbox matches, so there is no race between triggering the email and starting to wait.otp=true: only messages with a code or verification link.
curl "https://api.blinkinbox.club/api/v1/inboxes/$INBOX/messages/wait?timeout=45&from=github&subject=verify" \
-H "Authorization: Bearer $BLINKINBOX_API_KEY"OTP & link extraction
Each message is analysed when it arrives. Messages carry otp (best-guess code), codes (all candidates, best first), verificationLink, and classified links. Unsubscribe and social links are filtered out.
The detector scores numeric (482913, 482 913) and alphanumeric (G-4F7K2Q) candidates by nearby keywords (“code”, “verification”, “OTP”, “PIN”…), the subject line and layout. It discounts years, prices, phone numbers and order numbers.
{
"otp": "482913",
"verificationLink": "https://app.acme.dev/verify?token=…",
"messageId": "6700c1…",
"from": "no-reply@acme.dev",
"subject": "Your Acme verification code",
"receivedAt": "2026-10-06T12:00:02.000Z"
}Attachments
Every attachment is listed on the message with filename, contentType, size and stored. Only stored files can be downloaded; downloads are always served as application/octet-stream with Content-Disposition: attachment, so a browser never renders them.
skipped: "blocked_type": executables, scripts, installers, disk images, macro-enabled Office files, HTML and SVG are never stored. The type is checked by extension, MIME type and file signature, so renaming.exeto.pdfdoes not help.skipped: "too_large": files over 10 MB, or over 12 MB per message, are not stored.archive: true: zip, rar, 7z and similar are stored but not scanned inside. Open them only if you expected them.
{
"id": "6702f1c9a4e3b2d1c0f9e8a7",
"filename": "invoice.pdf",
"contentType": "application/pdf",
"size": 48213,
"stored": true,
"skipped": null,
"archive": false
}skipped reason.Endpoint reference
| Method | Path | Description |
|---|---|---|
| POST | /inboxes | Create an inbox |
| GET | /inboxes | List your inboxes |
| GET | /inboxes/{inbox} | Get one inbox |
| DELETE | /inboxes/{inbox} | Delete an inbox and its mail |
| GET | /inboxes/{inbox}/messages | List messages (filters: from, subject, since, unread) |
| GET | /inboxes/{inbox}/messages/wait | Long-poll for the next matching message |
| GET | /inboxes/{inbox}/otp | Latest OTP code / verification link |
| GET | /inboxes/{inbox}/messages/{id} | Full message: text, HTML, codes, links |
| GET | /inboxes/{inbox}/messages/{id}/attachments/{aid} | Download an attachment |
| DELETE | /inboxes/{inbox}/messages/{id} | Delete a message |
| GET | /domains | List custom domains |
| POST | /domains | Add a custom domain |
| POST | /domains/{id}/verify | Check DNS records |
| DELETE | /domains/{id} | Remove a domain |
| GET | /webhooks | List webhooks |
| POST | /webhooks | Create a webhook (secret returned once) |
| POST | /webhooks/{id}/test | Send a test event |
| DELETE | /webhooks/{id} | Delete a webhook |
| GET | /me | Plan, limits and usage |
Webhooks
Register an HTTPS endpoint to receive email.received events with the OTP already parsed. Deliveries time out after 8s and are retried 3 times with backoff. A webhook is disabled after 25 consecutive failures.
{
"id": "evt_9f2c41d0a7b3e8c15d6f2a90",
"type": "email.received",
"createdAt": "2026-10-06T12:00:03.120Z",
"data": {
"inbox": { "id": "66ff…", "address": "signup4k2j9x1a@blinkinbox.club", "label": "checkout-e2e" },
"message": {
"id": "6700…", "from": "no-reply@acme.dev", "subject": "Your Acme code",
"otp": "482913", "verificationLink": null, "receivedAt": "2026-10-06T12:00:02.000Z"
}
}
}Verifying signatures
Every request has BlinkInbox-Signature: t=<unix>,v1=<hex>, where v1 is HMAC-SHA256(secret, "<t>.<raw body>"). Verify against the raw body and reject old timestamps.
import crypto from "node:crypto";
// Express: app.post("/hooks/blinkinbox", express.raw({ type: "application/json" }), handler)
export function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // replay window
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}Custom domains
Pro and Scale plans can receive mail on their own domain (a subdomain such as test.yourco.com is ideal).
- Add the domain in the dashboard or with
POST /domains. - Publish the two records it returns: an MX to our mail host and a TXT at
_blinkinbox.<domain>. - Call verify. Once both records are visible, create inboxes with
{"domain": "test.yourco.com", "localPart": "anything"}.
Errors
Errors share one shape: {"error": {"code": "inbox_not_found", "message": "…"}}. Switch on code; it is stable.
| Status | Codes | What to do |
|---|---|---|
| 400 | invalid_ttl, ttl_exceeds_plan, invalid_prefix, invalid_local_part, invalid_domain, invalid_url | Fix the request |
| 401 | missing_api_key, invalid_api_key | Check the Authorization header |
| 403 | plan_required, session_required, custom_domain_requires_account | Upgrade or use the dashboard |
| 403 | attachment_blocked | The file type is blocked for safety; only its metadata is kept |
| 404 | inbox_not_found, message_not_found, otp_not_found, domain_not_found | Check the id / address |
| 408 | wait_timeout | Nothing arrived in time. Retry or raise timeout |
| 409 | address_in_use, domain_not_verified, domain_taken | Choose another or verify DNS |
| 413 | attachment_too_large | The file exceeded the size limit and was not stored |
| 429 | quota_exceeded, rate limit | Back off; see RateLimit headers |
Rate limits
Limits are per API key per minute: 60 (Developer), 600 (Pro), 3,000 (Scale). Daily inbox quotas: 3 (Developer), 10 (Pro), unlimited (Scale). Responses include standard RateLimit headers. Back off on 429. See pricing.
MCP
The same inboxes are exposed as tools for AI agents over the Model Context Protocol at https://api.blinkinbox.club/api/mcp. Clients that support OAuth (Claude, Claude Code, Cursor, VS Code) connect with one click: they open a BlinkInbox consent screen where you sign in and choose full or read-only access. Clients without OAuth can send an API key as a bearer token.
Approved clients are listed under Connected apps in the dashboard, where you can revoke them at any time. See the MCP setup guide for per-client instructions.