Track read and unread mail
Use an optional read label to find unread messages, mark mail read, and mark it unread again through HTTP or MCP.
Read as Markdown ↗Use a read label to keep a shared record of which messages you have read. Cherami has no built-in read state: this is an optional convention, and fetching content never changes labels.
Under this convention, a message with read is read; one without it is unread. You do not need a separate unread label. Newly arrived unlabeled mail qualifies as unread, as does older mail without read, even if someone previously fetched it. Cherami cannot reconstruct past reading activity.
Find unread messages
For the curl examples, set CHERAMI_API_KEY to your existing API key and INBOX_ID to the inbox you want to check. If you have not connected yet, follow the HTTP quickstart or MCP connection guide.
curl --get "https://cherami.to/v1/inboxes/$INBOX_ID/messages" \
-H "Authorization: Bearer $CHERAMI_API_KEY" \
--data-urlencode 'labels_none=read' \
--data-urlencode 'limit=20'With MCP, call list_messages with these arguments, replacing INBOX_ID with your inbox ID:
{"inbox_id":"INBOX_ID","labels_none":["read"],"limit":20}The returned messages are the newest matching received messages. An empty array means no messages match at that moment. Follow next_cursor with the same filter to see additional pages; start each new check without a cursor. Results are a live view, so arrivals and label changes can affect later pages.
For an unread count under this convention, use the same filter on the count endpoint:
curl --get "https://cherami.to/v1/inboxes/$INBOX_ID/messages/count" \
-H "Authorization: Bearer $CHERAMI_API_KEY" \
--data-urlencode 'labels_none=read'The MCP equivalent is count_messages:
{"inbox_id":"INBOX_ID","labels_none":["read"]}The returned count includes received messages without read. Without the filter, it counts all received messages in the inbox.
Read a message and mark it read
Set MESSAGE_ID to an ID from the listing and fetch its content:
curl "https://cherami.to/v1/messages/$MESSAGE_ID" \
-H "Authorization: Bearer $CHERAMI_API_KEY"With MCP, call get_message:
{"message_id":"MESSAGE_ID"}Check processing_status before treating content as available. If preparation is unfinished or failed, follow the receiving guidance.
After reading, explicitly add the label:
curl --request PATCH "https://cherami.to/v1/messages/$MESSAGE_ID" \
-H "Authorization: Bearer $CHERAMI_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"add_labels":["read"]}'With MCP, call update_message_labels:
{"message_id":"MESSAGE_ID","add_labels":["read"]}Both return the message ID and its resulting labels. For a previously unlabeled message:
{"id":"22222222-2222-4222-8222-222222222222","labels":["read"]}Other labels are preserved. The message no longer matches labels_none: ["read"].
Mark it unread again
Remove read from the same message:
curl --request PATCH "https://cherami.to/v1/messages/$MESSAGE_ID" \
-H "Authorization: Bearer $CHERAMI_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"remove_labels":["read"]}'With MCP, call update_message_labels:
{"message_id":"MESSAGE_ID","remove_labels":["read"]}The resulting labels array no longer contains read, so the message appears in the unread view again.
Agree on what read means
Labels are shared across the account, not private to a person, agent, or connection. If one agent adds read, every connection using this convention sees that message as read. The label belongs to the individual message, not its whole conversation; replies do not inherit it.
Track completed work separately from reading. A read label neither authorizes action nor prevents another agent from handling the same message.
For other label conventions and batch workflows, see Organize mail with labels. Exact update rules and filters live in the labels reference. For repeated checks, keep polling bounded as described in Receive and poll for mail.