nullz/docs
— api docs · v1

Disposable inboxes,over one API key.

The Nullz mail API is receive-only: create a mailbox, stream or poll it for messages, read or delete them. Copy-paste examples in cURL, JavaScript, Python and Go.

Quick start

Create a mailbox, then read it. Base URL: https://api.nullz.in

1 · create a mailbox
curl -X POST "https://api.nullz.in/api/v1/inbox" \
  -H "X-API-Key: $NULLZ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"demo@nullz.in","password":"s3cret"}'
2 · list messages
curl -X QUERY "https://api.nullz.in/api/v1/inbox" \
  -H "X-API-Key: $NULLZ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"demo@nullz.in","password":"s3cret"}'

Authentication

Send your key on every request as X-API-Key. Create, view, rotate, or revoke it from your API keys page, where it stays masked until you ask to see it. Store it in an environment variable, never in client-side code.

headers
X-API-Key: api_live_xxx
X-Mailbox-Password: s3cret   # only for password-protected mailboxes
Content-Type: application/json

A protected mailbox accepts the password either in the JSON body as password or in the X-Mailbox-Password header. Unprotected mailboxes can be read by anyone who knows the address — always set a password for anything real.

GET/api/v1/domains

List domains

Returns every domain you can create a mailbox on. Pick one before creating an inbox.

curl -X GET "https://api.nullz.in/api/v1/domains" \
  -H "X-API-Key: $NULLZ_API_KEY"
200 response
{
  "success": true,
  "data": ["nullz.in", "mail.nullz.in"]
}
POST/api/v1/inbox

Create a mailbox

Registers a receive-only mailbox. Add a password to lock the mailbox so only requests carrying that password can read it.

curl -X POST "https://api.nullz.in/api/v1/inbox" \
  -H "X-API-Key: $NULLZ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"demo@nullz.in","password":"optional-mailbox-password","label":"optional label"}'
request body
{
  "email": "demo@nullz.in",
  "password": "optional-mailbox-password",
  "label": "optional label"
}
200 response
{
  "success": true,
  "data": { "email": "demo@nullz.in", "protected": true }
}
  • 409 means the address already exists — reuse it with the same password.
  • Mailboxes are receive-only. There is no send endpoint.
QUERY/api/v1/inbox

List messages

Reads are done with the QUERY method and a JSON body, so mailbox credentials never end up in a URL or an access log.

curl -X QUERY "https://api.nullz.in/api/v1/inbox" \
  -H "X-API-Key: $NULLZ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"demo@nullz.in","password":"mailbox-password-if-protected"}'
request body
{
  "email": "demo@nullz.in",
  "password": "mailbox-password-if-protected"
}
200 response
{
  "success": true,
  "data": [
    {
      "id": "msg_01H...",
      "from": "no-reply@example.com",
      "subject": "Your code is 481920",
      "receivedAt": "2026-07-31T16:40:11.000Z",
      "hasAttachments": false
    }
  ]
}
  • 404 means the mailbox is not registered — create it first.
QUERY/api/v1/inbox/message

Read one message

Fetches the full message, including plain-text and HTML bodies. Empty bodies come back as null, never as a fake empty string.

curl -X QUERY "https://api.nullz.in/api/v1/inbox/message" \
  -H "X-API-Key: $NULLZ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"id":"msg_01H...","email":"demo@nullz.in","password":"mailbox-password-if-protected"}'
request body
{
  "id": "msg_01H...",
  "email": "demo@nullz.in",
  "password": "mailbox-password-if-protected"
}
200 response
{
  "success": true,
  "data": {
    "id": "msg_01H...",
    "from": "no-reply@example.com",
    "subject": "Your code is 481920",
    "textContent": "Your code is 481920",
    "htmlContent": "<p>Your code is <b>481920</b></p>"
  }
}
POST/api/v1/inbox/stream

Mint a live-stream token

Live updates beat polling: mint a short-lived token (~90s) that authorizes opening one Server-Sent Events stream for the mailbox. Same access rules as the other mail routes.

curl -X POST "https://api.nullz.in/api/v1/inbox/stream" \
  -H "X-API-Key: $NULLZ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"demo@nullz.in","password":"mailbox-password-if-protected"}'
request body
{
  "email": "demo@nullz.in",
  "password": "mailbox-password-if-protected"
}
200 response
{
  "success": true,
  "data": {
    "token": "eyJhbGciOi...",
    "expiresIn": 90,
    "email": "demo@nullz.in",
    "streamPath": "/api/v1/inbox/stream",
    "maxStreamSeconds": 900,
    "heartbeatSeconds": 20
  }
}
  • Treat the token as a one-shot credential — it can appear in access logs.
  • Limits: 3 concurrent streams per IP, 2 per mailbox.
GET/api/v1/inbox/stream?token=…

Live inbox stream (SSE)

Opens a text/event-stream connection. No API key or custom headers needed, so browser EventSource works directly. The stream ends after ~15 minutes — re-mint a token and reconnect.

event stream
event: ready
data: { "email": "demo@nullz.in" }

event: snapshot
data: [ { "id": "msg_01H...", "subject": "Your code is 481920" } ]

event: message
data: { "id": "msg_02J...", "subject": "Verify your login",
        "fromAddress": "no-reply@example.com",
        "toAddress": "demo@nullz.in",
        "createdAt": "2026-07-31T17:02:44.000Z" }

event: heartbeat
data: { "ts": 1785518564 }

event: end
data: { "reason": "max_duration" }
  • Events: ready, snapshot, message, heartbeat (~20s), end.
  • The token may also be sent as Authorization: Bearer <token>.
  • If you must poll instead, use 5–10s+ intervals and honour retryAfter.
DELETE/api/v1/inbox/message

Delete a message

Permanently removes a single message from the mailbox.

curl -X DELETE "https://api.nullz.in/api/v1/inbox/message" \
  -H "X-API-Key: $NULLZ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"id":"msg_01H...","email":"demo@nullz.in","password":"mailbox-password-if-protected"}'
request body
{
  "id": "msg_01H...",
  "email": "demo@nullz.in",
  "password": "mailbox-password-if-protected"
}
200 response
{ "success": true }
DELETE/api/v1/inbox

Delete a mailbox

Destroys the mailbox and every message it holds.

curl -X DELETE "https://api.nullz.in/api/v1/inbox" \
  -H "X-API-Key: $NULLZ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"demo@nullz.in","password":"mailbox-password-if-protected"}'
request body
{
  "email": "demo@nullz.in",
  "password": "mailbox-password-if-protected"
}
200 response
{ "success": true }

Errors

Every failure — including unhandled 404s — returns { "success": false, "error": "message" }.

400Malformed body — a required field is missing or not a string.
401Missing, revoked, or invalid X-API-Key.
403Wrong mailbox password, or the caller has no key, JWT or allowed origin.
404Mailbox or message does not exist.
409Mailbox already exists (on create).
429Rate limit exceeded — read retryAfter / Retry-After and back off.

Rate limits

Current defaults: unauthenticated website traffic shares a 2s minimum interval across v1 calls, mailbox creation is limited to one every 4s per IP (create is not stacked on the list interval), API keys get 60 requests/minute, and each IP has a 60/minute baseline. Auth allows 5 registrations/hour and 10 logins/minute. Every 429 returns { "success": false, "error": "rate limit exceeded", "retryAfter": N } plus a Retry-After header — back off exponentially, prefer SSE, and if you must poll, use 5–10s or slower.

suggested polling
let delay = 10_000;
for (;;) {
  const res = await poll();
  if (res.status === 429) {
    const { retryAfter } = await res.json();
    delay = Math.min(Math.max((retryAfter ?? 5) * 1000, delay * 2), 120_000);
  } else {
    delay = 10_000;
  }
  await new Promise((r) => setTimeout(r, delay));
}

Mailboxes are receive-only and messages are pruned automatically. Abuse reports go to abuse@nullz.in.