---
title: "Temp mail"
description: "Every operation in this group: what it accepts, what it returns and the errors it can answer with."
url: "https://openemail.uk/docs/api/reference/temp-mail"
area: "API"
category: "Reference"
---

# Temp mail

Every operation in this group: what it accepts, what it returns and the errors it can answer with.

## Operations

A free, anonymous disposable address, and the only resource on this API that a workspace key does not open.

It is reachable with no account on purpose. The thing being offered is an address to somebody who does not have one, and asking for a key first would make it a lead form wearing a tool's clothes. Two of the nine operations take no credential at all (listing the pool and creating an inbox), and the other seven take the INBOX TOKEN that creating one returned, in the same `Authorization: Bearer` header a key would travel in. No scope on any key reaches any of it, and a key sent here is refused by name rather than with a flat 401, because somebody holding both credentials on one host will send the wrong one.

THE ADDRESS IS NOT THE CREDENTIAL. It is typed into a signup form the moment it is issued and travels in a `To:` header from there, so it authorises nothing. The token is returned exactly once, by `POST /temp-mail/inboxes`, and nothing recovers it. The row keeps only an HMAC. A client that loses it has lost the inbox, which is the right outcome for a credential that reads somebody's mail.

A lease runs 60 minutes and extends in 60-minute steps to a hard 24 hours; an inbox holds at most fifty messages and drops the fifty-first rather than accepting mail it will not show. None of this is a mailbox: the mail belongs to no connection, is never threaded, labelled, indexed or summarised, and destroying an inbox deletes it rather than filing it. There is no send, and there will not be. A disposable inbox receives and nothing else.

### `GET /temp-mail/domains`

Domains a disposable address can be made on

Takes no credential at all: not an API key, not an inbox token. Start here: `POST /temp-mail/inboxes` accepts one of these names in `domain`, and anything else is refused with `unknown_domain`.

The list is the `TEMP_MAIL_DOMAINS` setting of this install, lowercased, in the order it was written. Nothing checks that a listed domain is verified or receiving mail, so an operator should list only one that is.

An empty list is a 200 with no rows rather than an error: "this install has no disposable domains" is a fact about the install, and a client should be able to read it without catching something. It is also the exact condition `POST /temp-mail/inboxes` answers 503 `not_configured` for, so a client that reads this first can say so before anybody types.

An inbox created without a `domain` goes on the first name in the list.

- Sends no credential.

**Returns**

- `200` `TempDomainList`: The pool, in the order it was configured. Possibly empty.

**Errors**

- The errors every operation can return: `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`tempMail.listDomains()`](https://openemail.uk/docs/sdk/reference/temp-mail#listDomains); CLI [`openemail temp-mail list-domains`](https://openemail.uk/docs/cli/reference/temp-mail#temp-mail-list-domains).

### `POST /temp-mail/inboxes`

Create a disposable inbox

Takes no credential and RETURNS one. The `token` is the lease itself, signed: the address, when it began, when it ends and the extensions spent. Nothing about the lease is stored on the server, so a lost token cannot be recovered, and every other operation on this resource except `GET /temp-mail/domains` needs it. Store it before you show the address to anybody.

The body is optional and so is every field in it: post nothing and you get a generated local-part on the first domain in the pool, leased for 60 minutes. A body that is not valid JSON is read as no body rather than refused, so a client that means "just give me an address" cannot fail on the shape of a request it did not care about.

Nothing reserves an address. Two callers who ask for the same `localPart` are both issued it, and each reads the mail that reaches it from the start of their own lease. Nothing holds an address back after a lease ends or its inbox is destroyed either. Leave `localPart` out for a generated name when the mail should reach you alone.

Nothing on this route is counted or rate limited, so there is no mint ceiling and no 429.

- Sends no credential.

**Request body**

- `domain` (`string`, up to 253 characters): One of the names from `GET /temp-mail/domains`, in any case. Omit it and the address goes on the first name in that list. A name that is not in the pool is refused with 422 `unknown_domain` rather than silently replaced. An address the caller did not ask for is one they have already copied into a form by the time they notice.
- `localPart` (`string`, up to 64 characters): The part before the @, lowercased. Omit it for a generated one, which is what most callers want: twelve characters from an alphabet with no vowels and no lookalikes. Longer than 64 characters is 422 `invalid_parameter`. Otherwise it has to be letters, digits, dots, dashes and underscores, starting and ending with a letter or digit, or it is 422 `invalid_address`. The names a domain owes to the internet (`postmaster`, `abuse` and their kin) are refused with 422 `reserved_address` rather than quietly rewritten. Nothing else is refused, because nothing holds an address: a name you choose may be one somebody else is reading at the same time, and that includes a name the domain’s OWNER uses for real mail (`legal@`, `privacy@`).
- `ttlMinutes` (`integer`, at least 1, at most 1440): How long the lease runs, from now. Defaults to 60. REFUSED rather than clamped: outside 1 to 1440 this answers 422 `invalid_parameter` naming the field, because a caller handed a day when they asked for a year has already shown somebody the expiry they asked for. The ceiling is on the whole lease and not on this call, which is the part worth knowing before you raise it: the 24 hours run from creation, so every hour taken here is an hour `extend` cannot add later. `extensionsLeft` does not show that. An inbox created with 1440 still reports 23, and none of them can add a minute.

**Returns**

- `201` `TempInboxWithToken`: The inbox, and its token.

**Errors**

- `422`: `unknown_domain`, `invalid_address` or `reserved_address`, or `invalid_parameter` for a body the schema refuses: a key it does not have, a `ttlMinutes` that is not a whole number from 1 to 1440, a `domain` over 253 characters or a `localPart` over 64. `invalid_parameter` carries `param` naming the field when there is one. The range is refused here, not clamped.
- `503`: `not_configured`: this install lists no disposable domain, so there is no address to hand out. Not worth retrying: an operator has to add one to `TEMP_MAIL_DOMAINS` first.
- The errors every operation can return: `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`tempMail.create()`](https://openemail.uk/docs/sdk/reference/temp-mail#create); CLI [`openemail temp-mail create`](https://openemail.uk/docs/cli/reference/temp-mail#temp-mail-create).

### `GET /temp-mail/inboxes/{id}`

Retrieve a disposable inbox

The lease and the counters, with no messages on it. `GET /temp-mail/inboxes/{id}/messages` is what a client polls. It carries `expiresAt` too, precisely so that watching an inbox does not cost two requests.

Authorised by the INBOX TOKEN and not by an API key: a workspace key sent here is refused with 401 `missing_inbox_token`, exactly as if nothing had been sent, and no scope on any key grants access to this resource. The token alone decides which inbox is read. The id in the path is not checked against it.

Once the lease is up the token answers 401 `inbox_expired` rather than an expired-inbox object. Expiry deletes nothing, but no token reaches mail that arrived before its own lease began.

`messageCount` is counted by reading every page of the inbox, so this is not the cheap call. When the install has no key to read the mailbox that runs the pool, it still answers 200, with `messageCount` 0.

- Authenticates with an inbox token.

**Path parameters**

- `id` (`string`, required): The `id` the inbox came back with: `tinb_` followed by the local part of the address. It is NOT checked against the bearer token, which alone decides which inbox is read, so a client juggling two inboxes has to keep each token with its own id. Send the right one anyway, because it is what makes a request readable in a log.

**Returns**

- `200` `TempInbox`: The inbox as it stands.

**Errors**

- `401`: `missing_inbox_token`: no bearer starting `oe_inbox_` was sent. A workspace API key gets this same answer, because only an inbox token is read on these routes. `inbox_expired`: the token is genuine and the expiry written inside it has passed.
- `404`: `resource_not_found`: this server did not sign the token, or, on a message route, the message is not one this lease can see. The inbox id in the path is never checked against the token, so it cannot cause a 404 on its own.
- The errors every operation can return: `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`tempMail.get()`](https://openemail.uk/docs/sdk/reference/temp-mail#get); CLI [`openemail temp-mail get`](https://openemail.uk/docs/cli/reference/temp-mail#temp-mail-get).

### `DELETE /temp-mail/inboxes/{id}`

Destroy a disposable inbox

Moves every message this lease can see to the bin of the mailbox that runs the pool. It does NOT end the lease: nothing about a lease is stored, so there is nothing to revoke, and the token keeps opening the address until the expiry written inside it. Mail that arrives after this call is listed as usual.

Nothing holds the address back afterwards either. It can be issued again at once, to anybody.

A tombstone rather than a 204, matching the rest of this API: the id comes back so a log line can name what went.

- Authenticates with an inbox token.

**Path parameters**

- `id` (`string`, required): The `id` the inbox came back with: `tinb_` followed by the local part of the address. It is NOT checked against the bearer token, which alone decides which inbox is read, so a client juggling two inboxes has to keep each token with its own id. Send the right one anyway, because it is what makes a request readable in a log.

**Returns**

- `200` `object`: Destroyed.
  - `object` (`string`, one of `"temp_inbox"`)
  - `id` (`string`)
  - `destroyed` (`boolean`, one of `true`)

**Errors**

- `401`: `missing_inbox_token`: no bearer starting `oe_inbox_` was sent. A workspace API key gets this same answer, because only an inbox token is read on these routes. `inbox_expired`: the token is genuine and the expiry written inside it has passed.
- `404`: `resource_not_found`: this server did not sign the token, or, on a message route, the message is not one this lease can see. The inbox id in the path is never checked against the token, so it cannot cause a 404 on its own.
- `503`: `not_configured`: this install has no key to read the mailbox that runs the pool, so no mail can be read or moved. Not worth retrying until an operator sets one.
- The errors every operation can return: `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`tempMail.delete()`](https://openemail.uk/docs/sdk/reference/temp-mail#delete); CLI [`openemail temp-mail delete`](https://openemail.uk/docs/cli/reference/temp-mail#temp-mail-delete).

### `POST /temp-mail/inboxes/{id}/extend`

Give an inbox more time

Pushes `expiresAt` an hour further out, up to 23 times, and returns a NEW `token` that carries the later expiry. No body. The old token keeps its old expiry, so replace it with this one.

There are two ceilings, and `extensionsLeft` counts only one of them. The new expiry is the smaller of "an hour from where it was" and "24 hours from when the inbox was created", so the last extension can buy less than an hour, and a lease that already reaches the 24 hours still accepts the call, spends an extension and buys nothing. `extensionsLeft` is 23 minus the extensions spent, so an inbox created with the whole 1440 still reports 23. Compare `expiresAt` with `createdAt` before offering more time.

Once all 23 are spent this answers 422 `extension_limit` for good. This response reads no mail, so `messageCount` is 0 and `lastMessageAt` is null on it whatever the inbox holds.

- Authenticates with an inbox token.

**Path parameters**

- `id` (`string`, required): The `id` the inbox came back with: `tinb_` followed by the local part of the address. It is NOT checked against the bearer token, which alone decides which inbox is read, so a client juggling two inboxes has to keep each token with its own id. Send the right one anyway, because it is what makes a request readable in a log.

**Returns**

- `200` `TempInboxWithToken`: The inbox, with the new expiry, one fewer extension left and a new token.

**Errors**

- `401`: `missing_inbox_token`: no bearer starting `oe_inbox_` was sent. A workspace API key gets this same answer, because only an inbox token is read on these routes. `inbox_expired`: the token is genuine and the expiry written inside it has passed.
- `404`: `resource_not_found`: this server did not sign the token, or, on a message route, the message is not one this lease can see. The inbox id in the path is never checked against the token, so it cannot cause a 404 on its own.
- `422`: `extension_limit`: all 23 extensions are spent. The only way on is a new inbox, or a real mailbox, which does not expire.
- The errors every operation can return: `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`tempMail.extend()`](https://openemail.uk/docs/sdk/reference/temp-mail#extend); CLI [`openemail temp-mail extend`](https://openemail.uk/docs/cli/reference/temp-mail#temp-mail-extend).

### `GET /temp-mail/inboxes/{id}/messages`

What has arrived

Newest first, metadata only, and the endpoint a client polls. Opening a message is what returns a body. Only mail delivered to this address since the lease began, and not moved to the bin, is listed.

`expiresAt` is on the list itself and not only on the inbox, so a countdown and the mail refresh together on one request.

A page holds up to fifty messages. When more have arrived, `hasMore` is true and `nextCursor` reads the next page, so nothing that reached the inbox is hidden. A page can hold fewer rows than `limit`, or none, while `hasMore` is still true, because mail to other addresses on the pool is read and dropped. Follow `nextCursor` until `hasMore` is false.

- Authenticates with an inbox token.

**Path parameters**

- `id` (`string`, required): The `id` the inbox came back with: `tinb_` followed by the local part of the address. It is NOT checked against the bearer token, which alone decides which inbox is read, so a client juggling two inboxes has to keep each token with its own id. Send the right one anyway, because it is what makes a request readable in a log.

**Query parameters**

- `limit` (`integer`, at least 1, at most 50, default `50`): Messages per page, newest first, 1 to 50.
- `cursor` (`string`, up to 512 characters): The previous page's `nextCursor`. Every message the inbox has received is reachable by following it.

**Returns**

- `200` `TempMessageList`: A page of the inbox, newest first, plus the lease.

**Errors**

- `401`: `missing_inbox_token`: no bearer starting `oe_inbox_` was sent. A workspace API key gets this same answer, because only an inbox token is read on these routes. `inbox_expired`: the token is genuine and the expiry written inside it has passed.
- `404`: `resource_not_found`: this server did not sign the token, or, on a message route, the message is not one this lease can see. The inbox id in the path is never checked against the token, so it cannot cause a 404 on its own.
- `422`: `invalid_parameter`: a `limit` that is not a whole number from 1 to 50, or a `cursor` over 512 characters. Any other query key is ignored.
- `503`: `not_configured`: this install has no key to read the mailbox that runs the pool, so no mail can be read or moved. Not worth retrying until an operator sets one.
- The errors every operation can return: `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`tempMail.listMessages()`](https://openemail.uk/docs/sdk/reference/temp-mail#listMessages), [`tempMail.listAllMessages()`](https://openemail.uk/docs/sdk/reference/temp-mail#listAllMessages), [`tempMail.iterateMessages()`](https://openemail.uk/docs/sdk/reference/temp-mail#iterateMessages); CLI [`openemail temp-mail list-messages`](https://openemail.uk/docs/cli/reference/temp-mail#temp-mail-list-messages).

### `GET /temp-mail/inboxes/{id}/messages/{messageId}`

Read one message

The list row plus the stored message. Reading it does NOT mark it seen: `seen` mirrors the unread state of the message in the mailbox that runs the pool, and nothing on these routes changes it.

Render `message.decodedBody`. `body` and `processedHtml` are empty strings on a locally-ingested message, which is every message that can reach a disposable inbox, so a client that reads either of those shows a blank page and blames the sender. The body is never cut, so `truncated` is always false.

A message this lease cannot see answers 404: it has to be delivered to this address, after this lease began, and not moved to the bin.

The HTML in there is attacker-supplied and arrived at an address anybody could name. Render it out of your own origin.

- Authenticates with an inbox token.

**Path parameters**

- `id` (`string`, required): The `id` the inbox came back with: `tinb_` followed by the local part of the address. It is NOT checked against the bearer token, which alone decides which inbox is read, so a client juggling two inboxes has to keep each token with its own id. Send the right one anyway, because it is what makes a request readable in a log.
- `messageId` (`string`, required): An `id` from the message list: the id of the thread the message sits in, in the mailbox that runs the pool, such as `thr_` and 24 hex characters. A message this lease cannot see answers 404, whatever the id.

**Returns**

- `200` `TempMessageWithBody`: The message, with its stored body.

**Errors**

- `401`: `missing_inbox_token`: no bearer starting `oe_inbox_` was sent. A workspace API key gets this same answer, because only an inbox token is read on these routes. `inbox_expired`: the token is genuine and the expiry written inside it has passed.
- `404`: `resource_not_found`: this server did not sign the token, or, on a message route, the message is not one this lease can see. The inbox id in the path is never checked against the token, so it cannot cause a 404 on its own.
- `503`: `not_configured`: this install has no key to read the mailbox that runs the pool, so no mail can be read or moved. Not worth retrying until an operator sets one.
- The errors every operation can return: `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`tempMail.getMessage()`](https://openemail.uk/docs/sdk/reference/temp-mail#getMessage); CLI [`openemail temp-mail get-message`](https://openemail.uk/docs/cli/reference/temp-mail#temp-mail-get-message).

### `DELETE /temp-mail/inboxes/{id}/messages/{messageId}`

Delete one message

Moves the message, attachments and all, to the bin of the mailbox that runs the pool, and from then on no lease lists it or opens it. Nothing on this API restores it.

`messageCount` goes down by one. There is no slot to free: the inbox keeps every message that reaches it, and the list pages through all of them.

- Authenticates with an inbox token.

**Path parameters**

- `id` (`string`, required): The `id` the inbox came back with: `tinb_` followed by the local part of the address. It is NOT checked against the bearer token, which alone decides which inbox is read, so a client juggling two inboxes has to keep each token with its own id. Send the right one anyway, because it is what makes a request readable in a log.
- `messageId` (`string`, required): An `id` from the message list: the id of the thread the message sits in, in the mailbox that runs the pool, such as `thr_` and 24 hex characters. A message this lease cannot see answers 404, whatever the id.

**Returns**

- `200` `object`: Deleted.
  - `object` (`string`, one of `"temp_message"`)
  - `id` (`string`)
  - `deleted` (`boolean`, one of `true`)

**Errors**

- `401`: `missing_inbox_token`: no bearer starting `oe_inbox_` was sent. A workspace API key gets this same answer, because only an inbox token is read on these routes. `inbox_expired`: the token is genuine and the expiry written inside it has passed.
- `404`: `resource_not_found`: this server did not sign the token, or, on a message route, the message is not one this lease can see. The inbox id in the path is never checked against the token, so it cannot cause a 404 on its own.
- `503`: `not_configured`: this install has no key to read the mailbox that runs the pool, so no mail can be read or moved. Not worth retrying until an operator sets one.
- The errors every operation can return: `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`tempMail.deleteMessage()`](https://openemail.uk/docs/sdk/reference/temp-mail#deleteMessage); CLI [`openemail temp-mail delete-message`](https://openemail.uk/docs/cli/reference/temp-mail#temp-mail-delete-message).

### `GET /temp-mail/inboxes/{id}/messages/{messageId}/attachments`

A message's attachments

Metadata only: the name, type and size of each part, and no bytes. No route on a disposable inbox serves the bytes of an attachment, and there is no per-part fetch.

`filename` and `mimeType` are whatever the sender declared. These files came from a stranger, to an address anybody could name, and nothing here is scanned.

- Authenticates with an inbox token.

**Path parameters**

- `id` (`string`, required): The `id` the inbox came back with: `tinb_` followed by the local part of the address. It is NOT checked against the bearer token, which alone decides which inbox is read, so a client juggling two inboxes has to keep each token with its own id. Send the right one anyway, because it is what makes a request readable in a log.
- `messageId` (`string`, required): An `id` from the message list: the id of the thread the message sits in, in the mailbox that runs the pool, such as `thr_` and 24 hex characters. A message this lease cannot see answers 404, whatever the id.

**Returns**

- `200` `TempAttachmentList`: The attachments, metadata only.

**Errors**

- `401`: `missing_inbox_token`: no bearer starting `oe_inbox_` was sent. A workspace API key gets this same answer, because only an inbox token is read on these routes. `inbox_expired`: the token is genuine and the expiry written inside it has passed.
- `404`: `resource_not_found`: this server did not sign the token, or, on a message route, the message is not one this lease can see. The inbox id in the path is never checked against the token, so it cannot cause a 404 on its own.
- `503`: `not_configured`: this install has no key to read the mailbox that runs the pool, so no mail can be read or moved. Not worth retrying until an operator sets one.
- The errors every operation can return: `500`, described in the [error catalog](https://openemail.uk/docs/api/errors).

Also available in: SDK [`tempMail.listAttachments()`](https://openemail.uk/docs/sdk/reference/temp-mail#listAttachments); CLI [`openemail temp-mail list-attachments`](https://openemail.uk/docs/cli/reference/temp-mail#temp-mail-list-attachments).

### Objects

#### `MessageEncryption`

`object`

What kind of encryption this message arrived carrying, read off its top-level `Content-Type` when it was ingested.

ABSENCE MEANS NOBODY LOOKED. The field is omitted on every message stored before detection existed. It never means "checked, and found none". A client reading a missing `encryption` as "this was plaintext" is asserting something no part of this system measured.

It is a statement about the ENVELOPE and not a verification. A message can be seen to be sealed without being opened, and those are different claims: nothing here says a signature checked out, says who holds a key, or licenses a padlock in a user interface.

SIGNED IS NOT SEALED, and that distinction is the whole reason `format` is an enum rather than a flag. For the three sealed formats the message's body fields are empty or hold PGP armor, and an empty body on such a message means "we cannot read this" rather than "there was nothing here". For the two signed formats the body is ordinary text and every consumer keeps working on it.

- `format` (`string`, required, one of `"pgp-mime"`, `"pgp-signed"`, `"pgp-inline"`, `"smime-encrypted"`, `"smime-signed"`): Which of the five envelope shapes was recognised. THREE of them mean the body is unreadable (`pgp-mime`, `pgp-inline` and `smime-encrypted`), and the other two, `pgp-signed` and `smime-signed`, mean the body arrived in the clear beside a detached signature and is read exactly like any other message. Branch on this value; branching on the mere presence of the object treats readable mail as unreadable.
- `detectedAt` (`string`, required): When the classification was made, on our clock at ingest. It dates OUR READING of the envelope and says nothing about when the message was encrypted or signed, or by whom.
- `rawRetained` (`boolean`, default `false`): Whether the original RFC822 bytes were kept, so that something holding the key could open the message later. Today it is always false. Nothing retains the raw message yet. It is published now so a client can start reading it rather than needing a second pass over every stored message the day that changes.
- `parts` (`object[]`): The envelope parts, each named by the id its bytes were written under. They are deliberately NOT in the message's attachment list (a PGP/MIME message would otherwise render two junk chips), so a sealed message commonly reports no attachments at all, and that emptiness is not evidence that nothing was attached. These ids are a CORRELATION KEY and not a handle: `GET /threads/{id}/messages/{messageId}/attachments` serves the display list, so it does not return them, and no other path in this API returns them either. `index` is the index into the original MIME parts.
  - `index` (`integer`, required)
  - `attachmentId` (`string`, required)
  - `role` (`string`, required, one of `"version"`, `"ciphertext"`, `"signature"`)

#### `TempAttachment`

`object`

- `object` (`string`, one of `"attachment"`)
- `attachmentId` (`string`): The id of the part in the mailbox that runs the pool: the message id, such as `msg_` and 24 hex, then `-` and the index of the part.
- `filename` (`string`): As the sender declared it, or a placeholder such as `attachment-1` when there was none.
- `mimeType` (`string`): As the sender declared it, or `application/octet-stream`. Do not trust it.
- `size` (`integer`): Decoded bytes.
- `body` (`string`): Always an empty string. No route on a disposable inbox serves the bytes.
- `headers` (`object[]`): Always empty.

#### `TempAttachmentList`

`object`

- `object` (`string`, one of `"list"`)
- `data` (`TempAttachment[]`)

#### `TempDomain`

`object`

A domain disposable addresses can be issued on, as the operator listed it in `TEMP_MAIL_DOMAINS`. Nothing checks that it is verified or receiving mail, so the list is only as good as the operator made it.

- `object` (`string`, one of `"temp_domain"`)
- `domain` (`string`): Lowercased. Pass it back as `domain`.

#### `TempDomainList`

`object`

- `object` (`string`, one of `"list"`)
- `data` (`TempDomain[]`)

#### `TempInbox`

`object`

- `object` (`string`, one of `"temp_inbox"`)
- `id` (`string`): The handle, `tinb_` followed by the local part of the address. It names the inbox in a path and authorises nothing: the path id is not even checked against the token. See the security scheme.
- `address` (`string`): Where mail should be sent. NOT a credential: it is typed into a signup form the moment it is issued and travels in a `To:` header from there.
- `domain` (`string`): The half of `address` after the @, split out so a client need not.
- `createdAt` (`string`, format `date-time`)
- `expiresAt` (`string`, format `date-time`): When the lease ends. Past this instant the token answers 401 `inbox_expired` on every path here. Nothing is deleted at expiry, but no token reaches mail that arrived before its own lease began, so the mail is out of reach through this API.
- `extensionsLeft` (`integer`): 23 minus the extensions spent. It counts calls and not time: `extend` never pushes the expiry past 24 hours from `createdAt`, so an inbox created with `ttlMinutes: 1440` reports 23 and none of them can add a minute. Compare `expiresAt` with `createdAt` before offering more time. At zero, `extend` answers 422 `extension_limit` and the only way on is a new inbox.
- `messageCount` (`integer`): How many messages the inbox is showing, counted across every page of the list. It goes down when one is deleted. It is always 0 on the responses to create and extend, which read no mail, and 0 when the install cannot read the mailbox that runs the pool.
- `messageLimit` (`integer`): The most messages one page of `GET /temp-mail/inboxes/{id}/messages` returns, published so a client does not hardcode our constant. It is not a ceiling: nothing past it is dropped, and `hasMore` with `nextCursor` reaches the rest.
- `lastMessageAt` (`string`, nullable, format `date-time`): When the most recent message arrived. Null on an inbox nothing has reached, and always null on the responses to create and extend.

#### `TempInboxWithToken`

`object`

- `object` (`string`, one of `"temp_inbox"`)
- `id` (`string`): The handle, `tinb_` followed by the local part of the address. It names the inbox in a path and authorises nothing: the path id is not even checked against the token. See the security scheme.
- `address` (`string`): Where mail should be sent. NOT a credential: it is typed into a signup form the moment it is issued and travels in a `To:` header from there.
- `domain` (`string`): The half of `address` after the @, split out so a client need not.
- `createdAt` (`string`, format `date-time`)
- `expiresAt` (`string`, format `date-time`): When the lease ends. Past this instant the token answers 401 `inbox_expired` on every path here. Nothing is deleted at expiry, but no token reaches mail that arrived before its own lease began, so the mail is out of reach through this API.
- `extensionsLeft` (`integer`): 23 minus the extensions spent. It counts calls and not time: `extend` never pushes the expiry past 24 hours from `createdAt`, so an inbox created with `ttlMinutes: 1440` reports 23 and none of them can add a minute. Compare `expiresAt` with `createdAt` before offering more time. At zero, `extend` answers 422 `extension_limit` and the only way on is a new inbox.
- `messageCount` (`integer`): How many messages the inbox is showing, counted across every page of the list. It goes down when one is deleted. It is always 0 on the responses to create and extend, which read no mail, and 0 when the install cannot read the mailbox that runs the pool.
- `messageLimit` (`integer`): The most messages one page of `GET /temp-mail/inboxes/{id}/messages` returns, published so a client does not hardcode our constant. It is not a ceiling: nothing past it is dropped, and `hasMore` with `nextCursor` reaches the rest.
- `lastMessageAt` (`string`, nullable, format `date-time`): When the most recent message arrived. Null on an inbox nothing has reached, and always null on the responses to create and extend.
- `token` (`string`, required): The credential: `oe_inbox_`, then the lease as base64url JSON, a dot, and a base64url HMAC-SHA256 signature over it. Create and extend are the only responses that carry one. Nothing about it is stored on the server, so nothing recovers it and nothing revokes it: a client that loses it has lost the inbox, and one that leaks it has leaked the inbox until `expiresAt`. Store it before anything is displayed.

#### `TempMessage`

`object`

A message in a disposable inbox: everything a list draws, and no body.

- `object` (`string`, one of `"temp_message"`)
- `id` (`string`): The id of the thread the message sits in, in the mailbox that runs the pool, such as `thr_` and 24 hex.
- `from` (`object`): As the message claimed. Nothing here has been authenticated. A disposable inbox publishes no SPF or DKIM verdict, and this is a display name rather than an identity.
  - `name` (`string`, nullable): Null when the sender sent none.
  - `email` (`string`)
- `to` (`string`): The address of the inbox, always, whatever form of it the sender used.
- `subject` (`string`)
- `snippet` (`string`): Plain text, HTML stripped, whitespace collapsed, capped at 400 characters. Usually enough to read a confirmation code out of without opening anything.
- `spam` (`boolean`): What the heuristic thought. A FLAG and never a filing decision. The one message a disposable inbox exists to receive is a machine-sent confirmation from a sender with no reputation, which is exactly what a spam filter is built to distrust, so hiding it would break the product. Grey it out; do not withhold it.
- `seen` (`boolean`): Whether the message is marked read in the mailbox that runs the pool. Retrieving it through this API does not change it, and nothing on these routes branches on it.
- `attachmentCount` (`integer`)
- `sizeBytes` (`integer`): Of the stored body, so a list can warn before opening a large one.
- `receivedAt` (`string`, format `date-time`): When WE received it, on our clock. The list is ordered on this rather than on the sender's `Date` header, which a sender controls.

#### `TempMessageList`

`object`

- `object` (`string`, one of `"list"`)
- `data` (`TempMessage[]`)
- `hasMore` (`boolean`): True when another page follows. Pass `nextCursor` back as `cursor` to read it.
- `nextCursor` (`string`, nullable): The id of the last row on this page, or null on the last page.
- `expiresAt` (`string`, format `date-time`): When the lease ends. Repeated from the inbox so that a countdown and the mail refresh on one request.

#### `TempMessageWithBody`

`object`

A message in a disposable inbox: everything a list draws, and no body.

- `object` (`string`, one of `"temp_message"`)
- `id` (`string`): The id of the thread the message sits in, in the mailbox that runs the pool, such as `thr_` and 24 hex.
- `from` (`object`): As the message claimed. Nothing here has been authenticated. A disposable inbox publishes no SPF or DKIM verdict, and this is a display name rather than an identity.
  - `name` (`string`, nullable): Null when the sender sent none.
  - `email` (`string`)
- `to` (`string`): The address of the inbox, always, whatever form of it the sender used.
- `subject` (`string`)
- `snippet` (`string`): Plain text, HTML stripped, whitespace collapsed, capped at 400 characters. Usually enough to read a confirmation code out of without opening anything.
- `spam` (`boolean`): What the heuristic thought. A FLAG and never a filing decision. The one message a disposable inbox exists to receive is a machine-sent confirmation from a sender with no reputation, which is exactly what a spam filter is built to distrust, so hiding it would break the product. Grey it out; do not withhold it.
- `seen` (`boolean`): Whether the message is marked read in the mailbox that runs the pool. Retrieving it through this API does not change it, and nothing on these routes branches on it.
- `attachmentCount` (`integer`)
- `sizeBytes` (`integer`): Of the stored body, so a list can warn before opening a large one.
- `receivedAt` (`string`, format `date-time`): When WE received it, on our clock. The list is ordered on this rather than on the sender's `Date` header, which a sender controls.
- `message` (`ThreadMessage`)
- `truncated` (`boolean`): Always false. The body is never cut on this resource.

#### `ThreadMessage`

`object`

A message, in whatever shape the mailbox stored it. The fields are deliberately not enumerated here. `encryption` is the one property this API describes, because it is the one whose absence cannot be guessed at safely.

- `encryption` (`MessageEncryption`)
