---
title: "Organizing and Searching Mail"
description: "List conversations a page at a time, file, read and star them, delete them, and search a mailbox or the whole organization."
canonical_url: "https://www.agent-identity.dev/docs/mail/organizing"
markdown_url: "https://www.agent-identity.dev/docs/mail/organizing.md"
last_updated: "2018-10-20"
x_farming_labs_generated_preamble: true
agent:
  tokenBudget: 1100
  task: "Find, read, file and delete an agent's mail, and search it."
  outcome: "GET /mail/threads pages through conversations newest first; PATCH marks and files them; GET /mail/search returns messages matching words, best first."
  appliesTo:
    package:
      - "@agentidentity/sdk"
      - "@aid/api"
  prerequisites:
    - "An agent with a mailbox that has some mail in it."
  files:
    - "apps/api/src/services/mail.ts"
    - "packages/db/src/repositories/mail-management.ts"
  sideEffects:
    - "Deleting a message or a conversation is permanent and removes its attachments from storage."
    - "Opening a conversation with mark_read=true marks every message in it read."
  verification:
    - description: "Page through a folder."
      expect: "GET /v1/identities/{id}/mail/threads?folder=inbox&limit=2 returns at most 2 items and a next_cursor until the last page, where it is null."
  rollback:
    - "Moving and marking are undone by moving or marking the other way. Deleting is not undone."
  failureModes:
    - symptom: "400 on a cursor."
      resolution: "A cursor is only good for the mailbox and query that gave it out. Start again without one."
    - symptom: "A draft or blocked message cannot be moved."
      resolution: "Drafts are sent or deleted, and mail blocked on arrival stays where it was put. Only the inbox, archive and spam can be chosen."
    - symptom: "Search finds nothing that is plainly there."
      resolution: "Search matches whole words, so `invoic` does not find `invoice`. Drafts are not searched until they are sent, and spam and blocked mail only with include_spam."
---

# Organizing and Searching Mail
URL: /docs/mail/organizing
LLM index: /llms.txt
Description: List conversations a page at a time, file, read and star them, delete them, and search a mailbox or the whole organization.
Related: /docs/mail/receiving, /docs/mail/sending, /docs/mail/drafts

<!-- farming-labs:agent-contract:start -->
## Agent Contract

Task: Find, read, file and delete an agent's mail, and search it.
Outcome: GET /mail/threads pages through conversations newest first; PATCH marks and files them; GET /mail/search returns messages matching words, best first.

### Applies To

- Package: `@agentidentity/sdk`, `@aid/api`

### Prerequisites

- An agent with a mailbox that has some mail in it.

### Files

- `apps/api/src/services/mail.ts`
- `packages/db/src/repositories/mail-management.ts`

### Side Effects

- Deleting a message or a conversation is permanent and removes its attachments from storage.
- Opening a conversation with mark_read=true marks every message in it read.

### Verification

- Page through a folder.
  - Expected: GET /v1/identities/{id}/mail/threads?folder=inbox&limit=2 returns at most 2 items and a next_cursor until the last page, where it is null.

### Rollback

- Moving and marking are undone by moving or marking the other way. Deleting is not undone.

### Failure Modes

- 400 on a cursor. — Recovery: A cursor is only good for the mailbox and query that gave it out. Start again without one.
- A draft or blocked message cannot be moved. — Recovery: Drafts are sent or deleted, and mail blocked on arrival stays where it was put. Only the inbox, archive and spam can be chosen.
- Search finds nothing that is plainly there. — Recovery: Search matches whole words, so `invoic` does not find `invoice`. Drafts are not searched until they are sent, and spam and blocked mail only with include_spam.
<!-- farming-labs:agent-contract:end -->

# Organizing and Searching Mail

## Folders

Every message is in one folder, from its owner's point of view: the **inbox** for what came in,
**sent** for what went out, **drafts** for what has not been sent, **spam** and **archive** for
what was filed there, and **blocked** for mail a rule refused on arrival. A conversation is in
every folder one of its messages is in.

```ts
await client.mail.listFolders(agent.id);
// [{ folder: "inbox", threads: 12, messages: 31, unread: 4 }, { folder: "sent", … }, …]
```

## Conversations

```ts
const page = await client.mail.listThreads({ identityId: agent.id, folder: "inbox", limit: 25 });
page.items;       // each: subject, messageCount, unreadCount, starred, lastMessageAt, latestFrom, latestSnippet, hasAttachments
page.nextCursor;  // null on the last page

for await (const thread of client.mail.iterateThreads({ identityId: agent.id, isRead: false })) { /* every unread one */ }
```

Newest activity first. Paging is by cursor, so mail that arrives while you page is never repeated or
skipped. Filters: `folder` (default: everything except spam, blocked and drafts), `isRead: false`
for conversations with something unread, `starred`, and `startDatetime` / `endDatetime` — RFC 3339,
or a date-time with no offset read in `tz` (`Europe/Tallinn`).

```bash
curl -sS ".../v1/identities/$AID_AGENT/mail/threads?folder=inbox&is_read=false&limit=25" -H "authorization: Bearer $AID_KEY"
```

`GET …/mail/threads/{id}?mark_read=true` opens a conversation and reads it; the response shows the
messages as they were before.

## Read, star, file

```ts
await client.mail.updateMessage(agent.id, messageId, { isRead: true, isStarred: true });
await client.mail.updateMessage(agent.id, messageId, { folder: "archive" });   // or "inbox", "spam"
await client.mail.updateThread(agent.id, threadId, { isRead: false });          // the whole conversation
await client.mail.updateThread(agent.id, threadId, { folder: "archive" });
```

What you send starts read; what arrives starts unread.

## Delete

```ts
await client.mail.deleteMessage(agent.id, messageId);  // and the conversation, if it was the last
await client.mail.deleteThread(agent.id, threadId);    // every message in it
```

Both are permanent and remove the attachments from storage.

## Search

```ts
const hits = await client.mail.search(agent.id, { q: '"purchase order" -draft', limit: 20 });
const all = await client.mail.searchOrganization({ q: "invoice 1142" }); // every mailbox; org key only
```

Whole words in any case over the subject, the body and every address and name. Several words mean
all of them, `"in quotes"` is a phrase, `-word` excludes. Best match first, then newest; each hit
carries a snippet. Drafts are not searched until they are sent, and spam and blocked mail only with
`includeSpam: true`. `q` is 1 to 500 characters and `limit` 1 to 100.

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
Docs-scoped sitemap: [/docs/sitemap.md](/docs/sitemap.md).
Well-known sitemap: [/.well-known/sitemap.md](/.well-known/sitemap.md).
