# History feed

history.list reports every change after a startHistoryId, as a sync client polls it, with no lag after a write.

A sync client reads `historyId` from `getProfile`, polls `history.list` from it and keeps the `historyId` each page returns. Make two changes and read them back as records:

```bash
curl -s -X POST "$RYSTIC_URL/_rystic/scenario" -d '{"name":"gmail_busy_inbox"}' -o /dev/null
G=(-H 'Authorization: Bearer ya29.placeholder')
START=$(curl -s "$RYSTIC_URL/gmail/v1/users/me/profile" "${G[@]}" | jq -r .historyId)
curl -s "$RYSTIC_URL/gmail/v1/users/me/messages/19a4c0d1e2f31005/modify" "${G[@]}" -H 'Content-Type: application/json' -d '{"removeLabelIds":["UNREAD"]}' | jq -c '{id}'
# -> {"id":"19a4c0d1e2f31005"}
curl -s -X POST "$RYSTIC_URL/gmail/v1/users/me/messages/19a4c0d1e2f31005/trash" "${G[@]}" | jq -c '{id}'
# -> {"id":"19a4c0d1e2f31005"}
curl -s "$RYSTIC_URL/gmail/v1/users/me/history?startHistoryId=$START" "${G[@]}" | jq -c '[.history[] | keys - ["id", "messages"]]'
# -> [["labelsRemoved"],["labelsAdded","labelsRemoved"]]
curl -s "$RYSTIC_URL/gmail/v1/users/me/history?startHistoryId=$START&historyTypes=labelAdded" "${G[@]}" | jq -c '[.history[] | keys - ["id", "messages"]]'
# -> [["labelsAdded"]]
curl -s "$RYSTIC_URL/gmail/v1/users/me/history?startHistoryId=$START&maxResults=1" "${G[@]}" | jq -c '{records: (.history | length), more: (.nextPageToken != null)}'
# -> {"records":1,"more":true}
curl -s "$RYSTIC_URL/gmail/v1/users/me/history" "${G[@]}" | jq -c '.error | {code, status}'
# -> {"code":400,"status":"INVALID_ARGUMENT"}
curl -s "$RYSTIC_URL/gmail/v1/users/me/history?startHistoryId=999999999" "${G[@]}" | jq -c '.error | {code, status}'
# -> {"code":404,"status":"NOT_FOUND"}
```

### The records each change lists

| Change | Record |
|---|---|
| inserting, importing or sending a message | `messagesAdded` |
| creating or updating a draft | `messagesAdded` |
| sending a draft | `messagesDeleted`, then `messagesAdded` |
| adding a label | `labelsAdded` |
| removing a label | `labelsRemoved` |
| trashing a message in the inbox | `labelsAdded` and `labelsRemoved` |
| deleting a message or a draft | `messagesDeleted` |

Each record carries its `id`, the `messages` it touched and the message's labels after the change.

### Filters and pages

`historyTypes` keeps only the kinds you name: `messageAdded`, `messageDeleted`, `labelAdded` or `labelRemoved`. `labelId` keeps only the changes to messages that carry that label. [Pagination](https://www.rystic.ai/docs/simulators/gmail.md#pagination) covers `maxResults`. A page with more carries `nextPageToken`, and the last one carries only the `historyId` to poll from next.

### Errors

| Request | Returns |
|---|---|
| no `startHistoryId` | 400 |
| a `startHistoryId` past the mailbox's current `historyId` | 404 `notFound` |

**Differs from Gmail:** `history.list` makes none of the refusals the other routes make. It returns the feed with no credential and reads any `userId` as `me` ([Limitations](https://www.rystic.ai/docs/simulators/gmail.md#limitations)).

### No lag, no push

A change appears in the feed as soon as its write returns, as on Gmail. `watch` and `stop` publish to a Cloud Pub/Sub topic and are not served; poll `history.list` instead.

---

Source: https://www.rystic.ai/docs/simulators/gmail/features/history-feed · Markdown: https://www.rystic.ai/docs/simulators/gmail/features/history-feed.md
