---
title: "Anonymous read of a public inbox's recent messages"
method: GET
path: "/public/emails/{email}/messages"
tags: ["messages"]
---

# Anonymous read of a public inbox's recent messages

`GET /public/emails/{email}/messages`

## Request

- Base URL: `https://dash.mailsocket.app/api/v1`
- URL: `https://dash.mailsocket.app/api/v1/public/emails/{email}/messages`
- Auth: none required

No authentication. Only works for inboxes created with
`POST /inboxes {"public": true}`. Inboxes are private by default,
and this is the only way to read one without a key. Use it for
demos and shared fixtures, never for real user signups: anyone who
knows the address can read the OTPs. Custom-domain (BYOD)
inboxes can never be resolved here even if `is_public` were somehow
forced true directly in the database: the read path also requires
the address to parse as a bare shared-domain (`MAIL_DOMAIN`) address
with no bound `Domain`.

Every miss (unknown address, private inbox, disabled, deleted,
malformed address, or a custom-domain address) returns
the SAME `404 not_found` body, so this endpoint can never be used to
enumerate which addresses exist.

The response includes `otp` and `magic_link` with the summary fields
(from, to, cc, subject, received_at). It never includes `text`,
`html`, `links`, `otp_context`, `otp_candidates` or `metadata`.

## Path parameters

- `email` string, required

## Query parameters

- `limit` integer

## Response `200`

Up to `limit` newest messages, newest first.

- object
  - `data` MessageSummary[], required
    - `id` string, required
    - `inbox_id` string, required
    - `from` string, required: The From header.
    - `to` string[], required
    - `cc` string[], required
    - `subject` string, required
    - `received_at` string, date-time, required
    - `status` 'received' | 'parsing' | 'parsed' | 'failed', required
    - `otp` string, nullable
    - `otp_confidence` number, nullable
    - `magic_link` string, nullable
    - `links` string[], nullable
    - `detected_otp` string, nullable
    - `otp_context` string, nullable: A short (≤160 character) plain-text snippet around the extracted `otp`, e.g. "Your verification code is 123456. It expires in 10 minutes." HTML is stripped, whitespace collapsed, and the window is bounded so the full body is never exposed. Null when there is no OTP or its location cannot be found. Best-effort, additive.
    - `otp_candidates` object[], nullable: Ranked plausible codes beyond the primary `otp` (the also-rans an agent can use to disambiguate), scored with the same logic as the primary extraction. Each item is `{code, confidence}`. Null/empty when there is only one plausible code or none.
      - `code` string, required
      - `confidence` number, required
    - `provider_hint` string, nullable: Best-effort label of the sending service derived from the From header domain (e.g. "github", "google", "stripe") via a small static map. Null when the domain is unknown. Convenience only; not authoritative, no network lookup.
    - `read` boolean: Whether the message has been marked read (`read_at IS NOT NULL`).
    - `metadata` object, required: The owning inbox's `metadata` (`{}` when unset), echoed on every authenticated read. Never present on the public-messages payload.

## Other responses

- `404`: Unknown, private (default), disabled, deleted, custom-domain, or malformed address. All cases return the same response.
- `429`: Per-IP rate limit exceeded (30/minute).

---

[API](https://skmtc.dev/mailsocket/apis/mailsocket-api.md) · [All operations](https://skmtc.dev/mailsocket/apis/mailsocket-api/llms.txt) · [OpenAPI document](https://skmtc.dev/mailsocket/apis/mailsocket-api/revisions/e579500dfb7e?raw)
