# Messages and threads

Insert, import, send, read in every format, label, trash and delete messages and threads, with the profile counts that follow each write.

Messages and threads are the mailbox itself: the simulator keeps each message as its RFC 5322 text and renders every read from it. Read a thread, move a message through its labels, then trash and delete it and check the counts the profile reports:

```bash
curl -s -X POST "$RYSTIC_URL/_rystic/scenario" -d '{"name":"gmail_busy_inbox"}' -o /dev/null
G=(-H 'Authorization: Bearer ya29.placeholder')
curl -s "$RYSTIC_URL/gmail/v1/users/me/threads?maxResults=2" "${G[@]}" | jq -c '[.threads[] | {id, snippet}]'
# -> [{"id":"19a4c0d1e2f30004","snippet":"Standup notes - Monday: three in flight, one blocked"},{"id":"19a4c0d1e2f30003","snippet":"Your flight ZX4821 is confirmed, seat 14C"}]
curl -s "$RYSTIC_URL/gmail/v1/users/me/threads/19a4c0d1e2f30004" "${G[@]}" | jq -c '{id, messages: [.messages[] | {id, labelIds}]}'
# -> {"id":"19a4c0d1e2f30004","messages":[{"id":"19a4c0d1e2f31005","labelIds":["INBOX","UNREAD","Label_13"]},{"id":"19a4c0d1e2f31006","labelIds":["SENT","Label_13"]},{"id":"19a4c0d1e2f31007","labelIds":["INBOX","UNREAD","Label_13"]}]}
curl -s "$RYSTIC_URL/gmail/v1/users/me/messages/19a4c0d1e2f31007/modify" "${G[@]}" -H 'Content-Type: application/json' -d '{"removeLabelIds":["UNREAD"],"addLabelIds":["STARRED"]}' | jq -c '{id, labelIds}'
# -> {"id":"19a4c0d1e2f31007","labelIds":["INBOX","Label_13","STARRED"]}
curl -s -X POST "$RYSTIC_URL/gmail/v1/users/me/messages/19a4c0d1e2f31007/trash" "${G[@]}" | jq -c '{id, labelIds}'
# -> {"id":"19a4c0d1e2f31007","labelIds":["TRASH","Label_13","STARRED"]}
curl -s -X DELETE "$RYSTIC_URL/gmail/v1/users/me/messages/19a4c0d1e2f31007" "${G[@]}" -o /dev/null -w '%{http_code}\n'
# -> 204
curl -s "$RYSTIC_URL/gmail/v1/users/me/messages/19a4c0d1e2f31007" "${G[@]}" | jq -c '.error | {code, status, message}'
# -> {"code":404,"status":"NOT_FOUND","message":"Requested entity was not found."}
curl -s "$RYSTIC_URL/gmail/v1/users/me/profile" "${G[@]}" | jq -c '{emailAddress, messagesTotal, threadsTotal}'
# -> {"emailAddress":"ada.lovelace@example.com","messagesTotal":8,"threadsTotal":6}
```

### Inserting, importing and sending

`POST /gmail/v1/users/me/messages` inserts a message: the whole RFC 5322 message as base64url `raw`, with the `labelIds` you give. It adds `SENT` when the `From` header is the mailbox's own address. `POST …/messages/import` imports one, and `POST …/messages/send` sends one, labeled `SENT`. Each also takes an upload ([Uploads](https://www.rystic.ai/docs/simulators/gmail/features/uploads.md)), and a send records an event in the [sent-mail log](https://www.rystic.ai/docs/simulators/gmail/features/sent-mail.md).

Inserted and sent messages join a thread only by the `threadId` you send, never by their `Subject` or `References` header. Without a `threadId` they start a thread of their own.

**Differs from Gmail:** an imported message joins no thread you can read ([Limitations](https://www.rystic.ai/docs/simulators/gmail.md#limitations)).

### Reading a message

`GET …/messages/{id}` takes a `format`:

- `full` returns the MIME part tree, each part's headers and body, with an attachment as an `attachmentId`.
- `metadata` returns only the headers `metadataHeaders` names.
- `minimal` returns the ids, labels and `snippet`.
- `raw` returns the base64url message as `raw`.

Every format carries `snippet` and `sizeEstimate`. `GET …/messages/{id}/attachments/{attachmentId}` returns an attachment's `data` and `size` by the id its part names. In the busy inbox, the invoice message carries a PDF and a CSV:

```bash
G=(-H 'Authorization: Bearer ya29.placeholder')
curl -s "$RYSTIC_URL/gmail/v1/users/me/messages/19a4c0d1e2f31003?format=full" "${G[@]}" | jq -c '[.payload.parts[] | {mimeType, filename}]'
# -> [{"mimeType":"text/plain","filename":""},{"mimeType":"application/pdf","filename":"invoice-4471.pdf"},{"mimeType":"text/csv","filename":"line-items.csv"}]
curl -s "$RYSTIC_URL/gmail/v1/users/me/messages/19a4c0d1e2f31003/attachments/MTlhNGMwZDFlMmYzMTAwMzoy" "${G[@]}" | jq -c '{size}'
# -> {"size":58}
```

### Labels, trash and delete

| Request | What it does |
|---|---|
| `modify` | adds and removes labels |
| `trash` | adds `TRASH` and removes `INBOX` |
| `untrash` | removes `TRASH` |
| `delete` | removes the message for good; reading it then returns 404 `notFound` |
| `batchModify`, `batchDelete` | take a list of ids and return 204 |

### Threads

`GET …/threads` lists threads newest first, each with its `snippet`. `GET …/threads/{id}` returns every message in the thread. A thread's `modify`, `trash`, `untrash` and `delete` act on all of its messages. A thread `modify` that names one label in both `addLabelIds` and `removeLabelIds` returns 400 `invalidArgument`, "Cannot both add and remove the same label".

### Profile

`GET …/profile` returns the mailbox's `emailAddress` and its current `historyId`, with `messagesTotal` and `threadsTotal` as they stand after the last write.

### Errors

Every JSON error is Gmail's envelope: `{"error": {"code", "message", "status", "errors": [{"domain", "reason", "message"}]}}`.

| Request | Returns |
|---|---|
| an unknown id | 404 `notFound`, "Requested entity was not found." |
| a `userId` the simulator does not take | 403 `forbidden` ([Auth](https://www.rystic.ai/docs/simulators/gmail.md#auth)) |

---

Source: https://www.rystic.ai/docs/simulators/gmail/features/messages-and-threads · Markdown: https://www.rystic.ai/docs/simulators/gmail/features/messages-and-threads.md
