API reference · v1

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.

Base URL: https://api.blinkinbox.club/api/v1 openapi.json

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.

header
Authorization: Bearer bb_live_xxxxxxxxxxxxxxxxxxxxxxxx
Keys are shown once and stored hashed. Keep them in your CI secrets, never in frontend code.

Quickstart

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.

FieldTypeDescription
prefixstringStart of the address. 8 random characters are appended: signup4k2j9x1a@blinkinbox.club
localPartstringExact local part. On custom domains, or on Pro for the shared domain.
domainstringA verified custom domain. Defaults to blinkinbox.club.
ttlMinutesintegerLifetime. Max 60 (Developer), 10,080 (Pro), 43,200 (Scale).
labelstringFree-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.
shell
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.

GET /inboxes/{inbox}/otp?timeout=30
{
  "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 .exe to .pdf does 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.
attachment
{
  "id": "6702f1c9a4e3b2d1c0f9e8a7",
  "filename": "invoice.pdf",
  "contentType": "application/pdf",
  "size": 48213,
  "stored": true,
  "skipped": null,
  "archive": false
}
Blocked files are treated as received mail: the message still arrives, and webhooks and MCP tools report the attachment with its skipped reason.

Endpoint reference

MethodPathDescription
POST/inboxesCreate an inbox
GET/inboxesList your inboxes
GET/inboxes/{inbox}Get one inbox
DELETE/inboxes/{inbox}Delete an inbox and its mail
GET/inboxes/{inbox}/messagesList messages (filters: from, subject, since, unread)
GET/inboxes/{inbox}/messages/waitLong-poll for the next matching message
GET/inboxes/{inbox}/otpLatest 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/domainsList custom domains
POST/domainsAdd a custom domain
POST/domains/{id}/verifyCheck DNS records
DELETE/domains/{id}Remove a domain
GET/webhooksList webhooks
POST/webhooksCreate a webhook (secret returned once)
POST/webhooks/{id}/testSend a test event
DELETE/webhooks/{id}Delete a webhook
GET/mePlan, 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.

payload
{
  "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).

  1. Add the domain in the dashboard or with POST /domains.
  2. Publish the two records it returns: an MX to our mail host and a TXT at _blinkinbox.<domain>.
  3. Call verify. Once both records are visible, create inboxes with {"domain": "test.yourco.com", "localPart": "anything"}.
DNS changes can take a few minutes to propagate. Verify is safe to call repeatedly.

Errors

Errors share one shape: {"error": {"code": "inbox_not_found", "message": "…"}}. Switch on code; it is stable.

StatusCodesWhat to do
400invalid_ttl, ttl_exceeds_plan, invalid_prefix, invalid_local_part, invalid_domain, invalid_urlFix the request
401missing_api_key, invalid_api_keyCheck the Authorization header
403plan_required, session_required, custom_domain_requires_accountUpgrade or use the dashboard
403attachment_blockedThe file type is blocked for safety; only its metadata is kept
404inbox_not_found, message_not_found, otp_not_found, domain_not_foundCheck the id / address
408wait_timeoutNothing arrived in time. Retry or raise timeout
409address_in_use, domain_not_verified, domain_takenChoose another or verify DNS
413attachment_too_largeThe file exceeded the size limit and was not stored
429quota_exceeded, rate limitBack 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.