Notes on threads
A note is a message for your team, kept on a thread beside its mail: what you already tried, who is waiting for an answer, what the next person should know. Notes are never sent. They are not part of any email, they are not in the thread’s replies or forwards, and they never leave mails.ai.
People write notes in the dashboard, and your code or agent writes them through the API.
In the dashboard
Open a thread. Its notes are listed under the mail, oldest first, each with its author and when it was written. Type in the note box under them to add one. Only your team sees notes.
Add a note
POST /v1/threads/{id}/notes takes a body of 1 to 10,000 characters and answers 201 with the new note. A thread holds up to 500 notes: past that the API answers 409 resource_cap_exceeded until one is deleted. A test key cannot add a note to a thread that holds live mail: that answers 403 live_mode_required.
import { createClient } from "@mailsai/sdk";
const client = createClient(); // reads MAILS_API_KEY
const threadId = "thrd_01J9X4B6T1";
const note = await client.threads.notes.create(threadId, {
body: "Customer was refunded on Oct 29. Waiting on their bank.",
});
console.log(note.id);from mailsai import create_client
client = create_client() # reads MAILS_API_KEY
thread_id = "thrd_01J9X4B6T1"
note = client.create_thread_note(thread_id, "Customer was refunded on Oct 29. Waiting on their bank.")
print(note["id"])curl -X POST "https://api.mails.ai/v1/threads/$THREAD_ID/notes" \
-H "Authorization: Bearer $MAILS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "body": "Customer was refunded on Oct 29. Waiting on their bank." }'mails threads notes add thrd_01J9X4B6T1 "Customer was refunded on Oct 29. Waiting on their bank."{
"id": "note_01J9X6Q4W7",
"object": "note",
"thread_id": "thrd_01J9X4B6T1",
"body": "Customer was refunded on Oct 29. Waiting on their bank.",
"author": { "kind": "key", "id": "key_01J9X0Z9Y8" },
"created_at": "2026-10-30T09:20:41.000Z"
}The note object
| Field | Description |
|---|---|
id | string The note’s id (note_…). |
object | string Always note. |
thread_id | string The thread the note is on. |
body | string What the note says, 1 to 10,000 characters. |
author | object Who wrote it. kind is user for a person, with their id, email and name, or key for an API key or a connected app, with its id. If a person’s account is erased, their notes stay and their id becomes null. |
created_at | string When the note was written. |
List a thread’s notes
GET /v1/threads/{id}/notes answers { "data": [...] } with every note on the thread, oldest first. A thread from the API also carries notes_count, so you can tell which threads have notes without asking for each.
import { createClient } from "@mailsai/sdk";
const client = createClient(); // reads MAILS_API_KEY
const { data: notes } = await client.threads.notes.list("thrd_01J9X4B6T1");
console.log(notes.map((n) => n.body));from mailsai import create_client
client = create_client() # reads MAILS_API_KEY
notes = client.list_thread_notes("thrd_01J9X4B6T1")["data"]
print([n["body"] for n in notes])curl "https://api.mails.ai/v1/threads/$THREAD_ID/notes" \
-H "Authorization: Bearer $MAILS_API_KEY"mails threads notes list thrd_01J9X4B6T1Delete a note
Deleting a note removes it for good. An API key with the manage scope, or a workspace owner or admin in the dashboard, can delete any note. Any other member deletes only the notes they wrote: anyone else’s answers 403 insufficient_scope. A test key cannot delete a note on a thread that holds live mail: that answers 403 live_mode_required.
import { createClient } from "@mailsai/sdk";
const client = createClient(); // reads MAILS_API_KEY
await client.threads.notes.remove("thrd_01J9X4B6T1", "note_01J9X6Q4W7");from mailsai import create_client
client = create_client() # reads MAILS_API_KEY
client.delete_thread_note("thrd_01J9X4B6T1", "note_01J9X6Q4W7")curl -X DELETE "https://api.mails.ai/v1/threads/$THREAD_ID/notes/$NOTE_ID" \
-H "Authorization: Bearer $MAILS_API_KEY"mails threads notes delete thrd_01J9X4B6T1 note_01J9X6Q4W7 --yes{ "id": "note_01J9X6Q4W7", "object": "note", "deleted": true }Events
Adding a note emits thread.note_added with the thread, its agent, the note’s id and its author. The note’s words are not in the event, so they never reach a webhook endpoint: read them with GET /v1/threads/{id}/notes if your code needs them. A webhook endpoint receives the event only when its event_types names it, as with the other thread events. Once an author’s account is erased, their id, email and name are null in the event.
{
"id": "evt_01J9X6R5X8",
"type": "thread.note_added",
"workspace_id": "wsk_01J9X0A1B2",
"agent_id": "agt_01J9X1C3V5",
"thread_id": "thrd_01J9X4B6T1",
"test_mode": false,
"data": {
"thread_id": "thrd_01J9X4B6T1",
"agent_id": "agt_01J9X1C3V5",
"note_id": "note_01J9X6Q4W7",
"author": { "kind": "key", "id": "key_01J9X0Z9Y8" }
},
"created_at": "2026-10-30T09:20:41.000Z"
}Routes
GET /List a thread's notes. Needs thev1/ threads/ {id}/ notes readscope.POST /Add a note to a thread. Needs thev1/ threads/ {id}/ notes managescope.DELETE /Delete a note. Needs thev1/ threads/ {id}/ notes/ {note_id} managescope.
Next steps
Full request and response shapes are in the API reference.
Was this page helpful?