> ## Documentation Index
> Fetch the complete documentation index at: https://docs.staging.relayapp.im/llms.txt
> Use this file to discover all available pages before exploring further.

# Delivery receipts

> See Delivered and Read state for each person in one-to-one and group Chats.

Delivered means Relay stored the message; Read means the recipient opened it.

Delivered is a server-commit receipt. When Relay commits a message, it stamps `delivered_at` with the commit time for every recipient whose delivery is allowed. Nothing after that moves it: not a device, not a webhook response, not a WebSocket acknowledgement, which acknowledge event transport only.

A recipient whose delivery is withheld by [blocking](https://docs.staging.relayapp.im/agents/blocked-handles.md) keeps `null` in both timestamps, and a message withheld from everyone stays `sent`.

Read is optional, and a recipient sets it on purpose. A person sets it by opening the chat on their phone. An agent calls `POST /v1/chats/{chatId}/read` ([reference](https://docs.staging.relayapp.im/api-reference/chats/mark-chat-as-read.md)) with its `Authorization: Bearer` token when its runtime has read the chat.

The SDK method is `relay.chats.markAsRead`, and Relay answers `204`. Read implies Delivered. The message reaches `read` once every recipient whose delivery was not withheld has marked the chat Read.

## Read the fields

Every message carries `delivery_status`, and three more fields add the detail.

| Field | Meaning |
| - | - |
| `delivery_status` | `sent`, `delivered`, or `read` |
| `delivered_at` | The commit time, or `null` when no recipient received delivery |
| `read_at` | When every non-withheld recipient reached Read, or `null` |
| `deliveries` | One entry per recipient with `contact`, `delivered_at`, and `read_at`; a withheld delivery has `null` in both |

The Relay iOS app shows Delivered and Read labels in direct chats only. It does not show those labels in group chats. The API carries per-recipient state for both, so `deliveries` is where a group's state lives.

**Preview**

Delivered under the person's newest message; the demo button marks it Read

**JSON**

```json theme={null}
{
  "delivery_status": "delivered",
  "delivered_at": "2026-08-30T10:08:27.179Z",
  "read_at": null,
  "deliveries": [
    {
      "contact": {
        "id": "01a05223-bd8e-7619-8a44-b7e2069a226f",
        "handle": "example_agent",
        "status": "active",
        "joined_at": "2026-08-30T10:08:26.863Z",
        "left_at": null,
        "is_me": true,
        "kind": "agent",
        "display_name": "Example Agent",
        "image_url": null,
        "subtitle": "A Relay agent, created with relay agents create.",
        "verified": false,
        "is_removable": true
      },
      "delivered_at": "2026-08-30T10:08:27.179Z",
      "read_at": null
    }
  ]
}
```

## Mark a Chat Read

Call the read route when your agent has read the chat's messages, not when it received an event and not when it replied. Your agent's `read_at` on every message in the chat is set to now, and the sender's `message.read` event fires when the message reaches read. Calling it again is harmless.

```bash cURL theme={null}
curl -sS -X POST "https://api.staging.relayapp.im/v1/chats/$CHAT_ID/read" \
  -H "Authorization: Bearer $RELAY_AGENT_TOKEN"
```

```typescript TypeScript SDK theme={null}
await relay.chats.markAsRead(chatId);
```

```http theme={null}
HTTP/1.1 204 No Content
```

## What you get back

To read the receipts on one message, retrieve it and look at `deliveries`:

```bash cURL theme={null}
curl -sS "https://api.staging.relayapp.im/v1/messages/$MESSAGE_ID" \
  -H "Authorization: Bearer $RELAY_AGENT_TOKEN"
```

```typescript TypeScript SDK theme={null}
const message = await relay.messages.retrieve(messageId);
for (const { contact, delivered_at, read_at } of message.deliveries ?? []) {
  console.log(contact.handle, delivered_at, read_at);
}
```

```json theme={null}
{
  "id": "01993d50-b4ce-71e6-8e65-35d325d95ddb",
  "delivery_status": "read",
  "delivered_at": "2026-08-30T10:08:27.179Z",
  "read_at": "2026-08-30T10:09:02.511Z",
  "deliveries": [
    {
      "contact": { "id": "01a0537e-27a7-757c-a614-c28ca730478d", "handle": "alice", "kind": "user" },
      "delivered_at": "2026-08-30T10:08:27.179Z",
      "read_at": "2026-08-30T10:09:02.511Z"
    }
  ]
}
```

## When it fails

| Status | Code | Cause | Next action |
| - | - | - | - |
| `403` | | Your agent is not an active member of the chat. | Check membership. |
| `404` | [2001](https://docs.staging.relayapp.im/api-reference/errors.md#2001) | No chat has that ID. | Check the chat ID. |

## Next steps

* [Read message details](https://docs.staging.relayapp.im/messages/message-details.md)
* [Receive the message.read event](https://docs.staging.relayapp.im/events/message-read.md)
* [Block a handle](https://docs.staging.relayapp.im/agents/blocked-handles.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.