Messages

A message is one email received by one of your inboxes. You can list and search an inbox's messages, read a message with its parsed bodies and authentication results, render its HTML safely, download attachments or the raw source, export a whole inbox, and delete messages.

Message endpoints accept a session token or an API key. A message in an inbox you do not own returns 404. Messages are removed when they are older than the inbox's retention_secs, or when the inbox expires.

The message model

The list endpoints return a short form. GET /v1/messages/:id returns the full form, which adds the bodies and parts.

List fields

  • Name
    id
    Type
    string
    Description

    Message id.

  • Name
    inbox_id
    Type
    string
    Description

    The inbox that received it.

  • Name
    received_at
    Type
    timestamp
    Description

    When it was received.

  • Name
    from_addr
    Type
    string | null
    Description

    Sender address from the From header.

  • Name
    from_name
    Type
    string | null
    Description

    Sender display name.

  • Name
    subject
    Type
    string | null
    Description

    Subject.

  • Name
    snippet
    Type
    string | null
    Description

    Short text preview.

  • Name
    size_bytes
    Type
    integer
    Description

    Size of the raw message.

  • Name
    has_attachments
    Type
    boolean
    Description

    Whether the message has attachments.

  • Name
    read_at
    Type
    timestamp | null
    Description

    When it was marked read.

Full message, additional fields

  • Name
    to_addrs
    Type
    array
    Description

    Recipient addresses.

  • Name
    auth_results
    Type
    object | null
    Description

    SPF, DKIM and DMARC results computed when the message arrived. See below.

  • Name
    text_plain
    Type
    string | null
    Description

    Plain-text body.

  • Name
    html_sanitized
    Type
    string | null
    Description

    HTML body after sanitising. Inline images still use cid: references here; use the HTML view to render it with images.

  • Name
    parts
    Type
    array
    Description

    MIME parts. Each has index (used in the download URL), content_type, filename, content_id, size_bytes and is_inline.

Subjects, names, snippets and filenames come from the sender. Display them as text, never as HTML.

Authentication results

  • Name
    spf
    Type
    object | null
    Description

    result and domain (the MAIL FROM domain, or the HELO name for an empty sender).

  • Name
    dkim
    Type
    array
    Description

    One entry per DKIM signature: result, domain (d=) and selector (s=).

  • Name
    dmarc
    Type
    object | null
    Description

    result, domain (the From domain) and policy (none, quarantine, reject, or null).

  • Name
    verified
    Type
    boolean
    Description

    Whether verification ran at all.

result uses the RFC 8601 verdict names: pass, fail, softfail, neutral, temperror, permerror, none.


GET/v1/inboxes/:id/messages

List messages

Returns a page of the inbox's messages, newest first. See pagination. Listing an inbox counts as activity and pushes its expiry forward.

Optional query parameters

  • Name
    limit
    Type
    integer
    Description

    Page size, 1–100. Default 25.

  • Name
    cursor
    Type
    string
    Description

    next_cursor from the previous page.

Request

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

Response

{
  "items": [
    {
      "id": "019a2f61-3e5a-7b12-8c4d-6e8f0a2b4c6d",
      "inbox_id": "019a2f50-1c3d-7e41-8b2a-4f6d8e0a1c3b",
      "received_at": "2026-10-26T08:05:12Z",
      "from_addr": "noreply@example.com",
      "from_name": "Example",
      "subject": "Your sign-in code",
      "snippet": "Your code is 481920. It expires in 10 minutes.",
      "size_bytes": 5821,
      "has_attachments": false,
      "read_at": null
    }
  ],
  "next_cursor": null
}

GET/v1/inboxes/:id/messages/search

Search messages

Full-text search over subject, snippet, sender address (whole, local part or domain) and sender name. The query uses web-search syntax: "exact phrase", -excluded, or. Results are best matches first, at most 100, with no cursor (next_cursor is always null).

Accounts only: anonymous sessions get 403 tier_required. Rate limited per tier (10 per minute for a free account).

Required query parameters

  • Name
    q
    Type
    string
    Description

    Search terms, 1–200 characters.

Optional query parameters

  • Name
    limit
    Type
    integer
    Description

    Result cap, 1–100. Default 25.

Request

GET
/v1/inboxes/:id/messages/search
curl -G https://mail.nullnet.sh/v1/inboxes/019a2f50-1c3d-7e41-8b2a-4f6d8e0a1c3b/messages/search \
  -H "Authorization: Bearer {token}" \
  --data-urlencode 'q="sign-in code" -newsletter'

GET/v1/messages/:id

Get a message

Returns the full message: list fields, recipients, authentication results, bodies and parts.

Request

GET
/v1/messages/:id
curl https://mail.nullnet.sh/v1/messages/019a2f61-3e5a-7b12-8c4d-6e8f0a2b4c6d \
  -H "Authorization: Bearer {token}"

Response

{
  "id": "019a2f61-3e5a-7b12-8c4d-6e8f0a2b4c6d",
  "inbox_id": "019a2f50-1c3d-7e41-8b2a-4f6d8e0a1c3b",
  "received_at": "2026-10-26T08:05:12Z",
  "from_addr": "noreply@example.com",
  "from_name": "Example",
  "subject": "Your sign-in code",
  "snippet": "Your code is 481920. It expires in 10 minutes.",
  "size_bytes": 5821,
  "has_attachments": false,
  "read_at": null,
  "to_addrs": ["alice@nullnet.sh"],
  "auth_results": {
    "spf": { "result": "pass", "domain": "example.com" },
    "dkim": [
      { "result": "pass", "domain": "example.com", "selector": "s1" }
    ],
    "dmarc": { "result": "pass", "domain": "example.com", "policy": "reject" },
    "verified": true
  },
  "text_plain": "Your code is 481920. It expires in 10 minutes.\n",
  "html_sanitized": "<p>Your code is <b>481920</b>.</p><img src=\"cid:logo@example.com\">",
  "parts": [
    {
      "index": 2,
      "content_type": "image/png",
      "filename": "logo.png",
      "content_id": "logo@example.com",
      "size_bytes": 2048,
      "is_inline": true
    }
  ]
}

GET/v1/messages/:id/html

Get the HTML view

Returns a complete HTML page with the sanitised body, ready for a sandboxed iframe. A message without HTML is rendered as preformatted plain text.

Inline (cid:) images are resolved when the page is served. Images up to 32 KiB are embedded as data: URIs. Larger ones become signed part URLs, valid for 10 minutes, that load without a token. References that match no image part are left as cid:.

The page is served with this Content Security Policy, which blocks scripts, forms and all network access except same-origin and data: images:

default-src 'none'; img-src 'self' data:; style-src 'unsafe-inline'; form-action 'none'; base-uri 'none'; sandbox

It also sets X-Content-Type-Options: nosniff and Referrer-Policy: no-referrer. An iframe src cannot carry the token, so fetch the page with your token and set it as the srcdoc of an iframe with an empty sandbox attribute (no allow-*). Do not insert html_sanitized into your own page. Signed image URLs expire, so fetch the view again rather than caching it.

Request

GET
/v1/messages/:id/html
curl https://mail.nullnet.sh/v1/messages/019a2f61-3e5a-7b12-8c4d-6e8f0a2b4c6d/html \
  -H "Authorization: Bearer {token}"

GET/v1/messages/:id/parts/:n

Download a part

Downloads one MIME part by its index. Rate limited per tier (attachment downloads per hour).

Only images whose bytes match their type (PNG, JPEG, GIF, WebP) are served inline with their content type. Everything else is served as application/octet-stream with Content-Disposition: attachment. Responses carry X-Content-Type-Options: nosniff.

Optional query parameters

  • Name
    exp
    Type
    integer
    Description

    Expiry of a signed URL, Unix seconds.

  • Name
    sig
    Type
    string
    Description

    Signature of a signed URL. With exp, replaces the token. Signed URLs are created by the HTML view and work for image parts only.

Request

GET
/v1/messages/:id/parts/:n
curl -OJ https://mail.nullnet.sh/v1/messages/019a2f61-3e5a-7b12-8c4d-6e8f0a2b4c6d/parts/2 \
  -H "Authorization: Bearer {token}"

GET/v1/messages/:id/raw

Download the raw message

Downloads the original message bytes as received (RFC 5322), as message/rfc822 with an attachment disposition and a <id>.eml file name. Counts against the raw-download budget per hour.

Request

GET
/v1/messages/:id/raw
curl -o message.eml https://mail.nullnet.sh/v1/messages/019a2f61-3e5a-7b12-8c4d-6e8f0a2b4c6d/raw \
  -H "Authorization: Bearer {token}"

GET/v1/inboxes/:id/export

Export an inbox

Streams every live message of the inbox as one mboxrd file (application/mbox, file name <inbox id>.mbox). Line endings are converted to LF, and lines starting with From (after any >) are quoted with >. Counts as one raw download.

Request

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

POST/v1/messages/:id/read

Mark as read

Sets read_at. Returns 204.

Request

POST
/v1/messages/:id/read
curl -X POST https://mail.nullnet.sh/v1/messages/019a2f61-3e5a-7b12-8c4d-6e8f0a2b4c6d/read \
  -H "Authorization: Bearer {token}"

DELETE/v1/messages/:id

Delete a message

Deletes the message and updates the inbox counters. Other clients watching the inbox over the WebSocket receive a message_deleted event. Returns 204.

Request

DELETE
/v1/messages/:id
curl -X DELETE https://mail.nullnet.sh/v1/messages/019a2f61-3e5a-7b12-8c4d-6e8f0a2b4c6d \
  -H "Authorization: Bearer {token}"