Inboxes

An inbox is one email address. It belongs to the session that created it, or to the account if the session was logged in, and it expires after the retention period of your tier. Reading an inbox counts as activity and pushes its expiry forward.

Inbox endpoints accept a session token or an API key. An inbox owned by someone else returns 404.

The inbox model

  • Name
    id
    Type
    string
    Description

    Inbox id.

  • Name
    address
    Type
    string
    Description

    Full address, for example kqv-7f3m2a@mail.nullnet.sh.

  • Name
    expires_at
    Type
    timestamp
    Description

    When the inbox and its mail are removed, unless it is used or extended first.

  • Name
    retention_secs
    Type
    integer
    Description

    The retention window of the owner's tier. Messages older than this are removed, and activity pushes expires_at to at least now plus this window.

  • Name
    message_count
    Type
    integer
    Description

    Live messages.

  • Name
    storage_bytes
    Type
    integer
    Description

    Bytes stored in the inbox.

  • Name
    created_at
    Type
    timestamp
    Description

    Creation time.

Addresses

  • Addresses are lowercased. A +tag is ignored when resolving an address: mail to bob+news@nullnet.sh arrives at bob@nullnet.sh.
  • Without a chosen local part, the server picks a random one: three letters, a dash and six letters or digits (kqv-7f3m2a).
  • A local part is at most 64 characters of the usual address characters, with no leading, trailing or doubled dot.
  • Reserved addresses. On the system domains, these local parts belong to the operator and cannot be created or claimed: info, postmaster, abuse, admin, administrator, hostmaster, webmaster, security, support, root, noreply, no-reply, billing, sales. Creating one returns 409 conflict, and availability is always false.
  • Unclaimed mail. Mail to an address on a system domain that nobody has created yet is kept in an unowned inbox for 30 minutes. Creating that address claims it, with the mail already received.

GET/v1/inboxes/available

Check availability

Whether an address can be created now. Credentials are optional. This endpoint is strictly rate limited, per caller and per client network (10 per minute for an anonymous session).

An unclaimed address with mail waiting counts as available. A reserved address never does. An unknown domain returns false.

Required query parameters

  • Name
    local
    Type
    string
    Description

    Local part.

  • Name
    domain
    Type
    string
    Description

    Domain.

Response

  • Name
    available
    Type
    boolean
    Description

    Whether POST /v1/inboxes would succeed for this address.

Request

GET
/v1/inboxes/available
curl -G https://mail.nullnet.sh/v1/inboxes/available \
  -d local=alice \
  -d domain=nullnet.sh

Response

{ "available": true }

POST/v1/inboxes

Create an inbox

Creates an inbox. The body is optional: with no body you get a random address on the first system domain. Returns 201 with the inbox.

  • 400 bad_request: invalid local part, or a domain you cannot use.
  • 409 conflict: the address is owned by someone else, or reserved.
  • 402 payment_required: you are at your tier's live-inbox limit. Delete one or upgrade.
  • 429 rate_limited: you are at your tier's inboxes-per-hour limit.

Optional attributes

  • Name
    local
    Type
    string
    Description

    The local part you want. Random when omitted.

  • Name
    domain
    Type
    string
    Description

    A system domain from GET /v1/domains, or a verified custom domain you own. The first system domain when omitted.

Request

POST
/v1/inboxes
curl https://mail.nullnet.sh/v1/inboxes \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{"local": "alice", "domain": "nullnet.sh"}'

Response

{
  "id": "019a2f50-1c3d-7e41-8b2a-4f6d8e0a1c3b",
  "address": "alice@nullnet.sh",
  "expires_at": "2026-10-26T09:00:00Z",
  "retention_secs": 3600,
  "message_count": 0,
  "storage_bytes": 0,
  "created_at": "2026-10-26T08:00:00Z"
}

GET/v1/inboxes

List inboxes

Returns your live inboxes as an array. For an anonymous session, these are the session's inboxes. For a logged-in session, the account's inboxes plus any the session still owns itself. For an API key, the account's inboxes.

Request

GET
/v1/inboxes
curl https://mail.nullnet.sh/v1/inboxes \
  -H "Authorization: Bearer {token}"

Response

[
  {
    "id": "019a2f50-1c3d-7e41-8b2a-4f6d8e0a1c3b",
    "address": "alice@nullnet.sh",
    "expires_at": "2026-10-26T09:00:00Z",
    "retention_secs": 3600,
    "message_count": 3,
    "storage_bytes": 48211,
    "created_at": "2026-10-26T08:00:00Z"
  }
]

GET/v1/inboxes/:id

Get an inbox

Returns one inbox with its counters. Reading the inbox counts as activity: expires_at moves to at least now plus retention_secs, and the response shows the new value.

Request

GET
/v1/inboxes/:id
curl https://mail.nullnet.sh/v1/inboxes/019a2f50-1c3d-7e41-8b2a-4f6d8e0a1c3b \
  -H "Authorization: Bearer {token}"

POST/v1/inboxes/:id/extend

Extend an inbox

Resets the expiry to a full retention window from now. Expiry never moves backwards. Returns the inbox.

Request

POST
/v1/inboxes/:id/extend
curl -X POST https://mail.nullnet.sh/v1/inboxes/019a2f50-1c3d-7e41-8b2a-4f6d8e0a1c3b/extend \
  -H "Authorization: Bearer {token}"

DELETE/v1/inboxes/:id

Delete an inbox

Deletes the inbox. It disappears from the API at once, and its mail is purged shortly after. Returns 204.

Request

DELETE
/v1/inboxes/:id
curl -X DELETE https://mail.nullnet.sh/v1/inboxes/019a2f50-1c3d-7e41-8b2a-4f6d8e0a1c3b \
  -H "Authorization: Bearer {token}"

Custom domains

Premium accounts can receive mail on their own domains (up to 10). Other tiers get 403 tier_required. Custom domains are managed from a logged-in session, not with an API key.

  1. Register the domain with POST /v1/domains. The response lists two DNS records to publish: an MX record pointing at the NULL mail server, and a TXT record grrla-verify=<token> at _grrla.<domain>.
  2. Publish both records at your DNS provider.
  3. Call POST /v1/domains/:id/verify. The domain is verified when the TXT token matches and an MX record points at the server.

Until a domain is verified, mail to it is refused and inboxes cannot be created on it. A registration that is not verified within seven days is dropped and the name is freed. Verified domains are re-checked daily. If the TXT or MX record disappears, the domain becomes unverified and mail is refused again until verify passes.

On a verified custom domain, every address belongs to you. Only you can create inboxes on it, reserved local parts do not apply, and mail to an address you have not created yet creates an inbox owned by your account, with your retention.

The custom domain model

  • Name
    id
    Type
    string
    Description

    Domain id.

  • Name
    name
    Type
    string
    Description

    Domain name.

  • Name
    verified
    Type
    boolean
    Description

    Whether mail is accepted.

  • Name
    verified_at
    Type
    timestamp | null
    Description

    When verification passed.

  • Name
    created_at
    Type
    timestamp
    Description

    Registration time.

  • Name
    dns
    Type
    array
    Description

    Records to publish. Each has kind (MX or TXT), name (owner name) and value.


POST/v1/domains

Register a custom domain

Registers a domain and returns the records to publish. Returns 201.

  • 400 bad_request: not a usable mail domain.
  • 402 payment_required: you are at your custom-domain limit.
  • 403 tier_required: your tier has no custom domains.
  • 409 conflict: the domain is already registered.

Required attributes

  • Name
    name
    Type
    string
    Description

    The domain, for example mail.example.com.

Request

POST
/v1/domains
curl https://mail.nullnet.sh/v1/domains \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{"name": "mail.example.com"}'

Response

{
  "id": "019a2f52-7a10-7c33-9e4b-2d5f7a9c1e3f",
  "name": "mail.example.com",
  "verified": false,
  "verified_at": null,
  "created_at": "2026-10-26T08:00:00Z",
  "dns": [
    { "kind": "MX", "name": "mail.example.com", "value": "10 mail.nullnet.sh" },
    { "kind": "TXT", "name": "_grrla.mail.example.com", "value": "grrla-verify=4f1c9a7e2b6d8f0a3c5e7b9d1f2a4c6e" }
  ]
}

GET/v1/domains/mine

List your custom domains

Returns your custom domains with their records, as an array of custom domain objects.

Request

GET
/v1/domains/mine
curl https://mail.nullnet.sh/v1/domains/mine \
  -H "Authorization: Bearer {token}"

POST/v1/domains/:id/verify

Verify a custom domain

Looks up the TXT and MX records now and marks the domain verified when both match. Limited to 20 checks per hour.

Response

  • Name
    verified
    Type
    boolean
    Description

    Verification state after this check.

  • Name
    txt_ok
    Type
    boolean
    Description

    Whether the TXT record was found with the right token.

  • Name
    mx_ok
    Type
    boolean
    Description

    Whether an MX record points at the server.

  • Name
    txt_found
    Type
    array
    Description

    TXT strings seen at _grrla.<domain>.

  • Name
    mx_found
    Type
    array
    Description

    MX hosts seen.

  • Name
    error
    Type
    string | null
    Description

    Resolver error, if a lookup failed.

Request

POST
/v1/domains/:id/verify
curl -X POST https://mail.nullnet.sh/v1/domains/019a2f52-7a10-7c33-9e4b-2d5f7a9c1e3f/verify \
  -H "Authorization: Bearer {token}"

Response

{
  "verified": true,
  "txt_ok": true,
  "mx_ok": true,
  "txt_found": ["grrla-verify=4f1c9a7e2b6d8f0a3c5e7b9d1f2a4c6e"],
  "mx_found": ["mail.nullnet.sh"],
  "error": null
}

DELETE/v1/domains/:id

Remove a custom domain

Removes the domain and deletes its inboxes. Returns 204.

Request

DELETE
/v1/domains/:id
curl -X DELETE https://mail.nullnet.sh/v1/domains/019a2f52-7a10-7c33-9e4b-2d5f7a9c1e3f \
  -H "Authorization: Bearer {token}"