Assigning threads
A thread can be assigned to one member of your workspace, so everyone can see who is handling it. People assign threads in the dashboard, and your code or agent can assign them through the API. Assigning a thread changes nothing about the mail itself: the thread stays in its folder, and nobody is emailed.
In the dashboard
- In the inbox, press the assignee’s mark on a row, open a thread’s … menu and choose Assign to…, or press A on the selected thread, then pick a teammate. Unassigned clears it.
- On a thread’s own page, the assignee picker does the same.
- To assign several threads at once, select them with X or their checkboxes and choose Assign in the bar at the bottom.
- Press / and the Assigned to me quick filter shows only your threads.
Every shortcut is on Keyboard shortcuts.
With the API
Send assignee to PATCH /v1/threads/{id}: a member’s email address or their usr_ id. Send null to unassign the thread. Assigning needs the same manage scope as changing a thread’s folder or labels, and a key tied to one agent can assign only that agent’s threads. An app connected by sign-in can also send me, the person who signed in; a plain API key has no person behind it, so me from one is a 422.
import { createClient } from "@mailsai/sdk";
const client = createClient(); // reads MAILS_API_KEY
const threadId = "thrd_01J9X4B6T1";
// Assign a thread to a teammate, by email or by usr_ id
await client.threads.update(threadId, {
assignee: "ada@acme.com",
});
// Unassign it
await client.threads.update(threadId, { assignee: null });from mailsai import create_client
client = create_client() # reads MAILS_API_KEY
thread_id = "thrd_01J9X4B6T1"
# Assign a thread to a teammate, by email or by usr_ id
client.update_thread(thread_id, assignee="ada@acme.com")
# Unassign it
client.update_thread(thread_id, assignee=None)# Assign a thread to a teammate, by email or by usr_ id
curl -X PATCH "https://api.mails.ai/v1/threads/$THREAD_ID" \
-H "Authorization: Bearer $MAILS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "assignee": "ada@acme.com" }'
# Unassign it
curl -X PATCH "https://api.mails.ai/v1/threads/$THREAD_ID" \
-H "Authorization: Bearer $MAILS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "assignee": null }'# Assign a thread to a teammate, by email or by usr_ id
mails threads assign thrd_01J9X4B6T1 ada@acme.com
# Unassign it
mails threads assign thrd_01J9X4B6T1 --noneOnly a member of the workspace can be assigned. Anyone else is refused with a 422 whose code is assignee_not_member, and the thread keeps the assignee it had. A test key cannot assign or unassign a thread that holds live mail: that answers 403 live_mode_required.
The answer is the thread and previous, which holds what the request changed. previous.assignee is who had the thread before, or null for nobody, so sending that person’s id or null undoes the change.
Who can be assigned
GET /v1/members lists the members of the workspace, with the id, email address, name and role of each. It needs the read scope, so an agent can look up a teammate before it assigns a thread.
import { createClient } from "@mailsai/sdk";
const client = createClient(); // reads MAILS_API_KEY
const { data: members } = await client.members.list();
console.log(members.map((m) => m.email));from mailsai import create_client
client = create_client() # reads MAILS_API_KEY
members = client.list_members()["data"]
print([m["email"] for m in members])curl "https://api.mails.ai/v1/members" \
-H "Authorization: Bearer $MAILS_API_KEY"mails members list{
"data": [
{ "id": "usr_01J9X2K7Q8", "email": "ada@acme.com", "name": "Ada Park", "role": "owner" },
{ "id": "usr_01J9X3M2D4", "email": "sam@acme.com", "name": "Sam Lee", "role": "member" }
]
}On the thread
Every thread the API returns, from a list, a single thread or an update, carries its assignee and assigned_at, when it was assigned. assignee is null while nobody has the thread. notes_count is how many notes the team has left on it.
{
"id": "thrd_01J9X4B6T1",
"subject": "Refund for order 1041",
"folder": "inbox",
"assignee": { "id": "usr_01J9X2K7Q8", "email": "ada@acme.com", "name": "Ada Park" },
"assigned_at": "2026-10-30T09:14:02.000Z",
"notes_count": 1,
"...": "the thread's other fields"
}Find assigned threads
GET /v1/threads takes an assignee filter: a member’s email address or usr_ id for their threads, none for threads nobody has, or any for threads somebody has.
import { createClient } from "@mailsai/sdk";
const client = createClient(); // reads MAILS_API_KEY
// Threads nobody has taken yet
const { data: threads } = await client.threads.list({ assignee: "none" });
console.log(threads.length);from mailsai import create_client
client = create_client() # reads MAILS_API_KEY
# Threads nobody has taken yet
threads = client.list_threads(assignee="none")["data"]
print(len(threads))# Threads nobody has taken yet
curl -G "https://api.mails.ai/v1/threads" \
-H "Authorization: Bearer $MAILS_API_KEY" \
--data-urlencode "assignee=none"# Threads nobody has taken yet
mails threads list --assignee noneSearch takes the same filter as words: assignee:ada@acme.com, is:assigned and is:unassigned work in the dashboard’s search box and in q on GET /v1/search (see search operators). assignee:me means you, so it works where a person is signed in: in the dashboard or through an app connected by sign-in, not with a plain API key.
Events
Assigning or reassigning a thread emits thread.assigned, and clearing it emits thread.unassigned, once for each real change: assigning the person a thread already has emits nothing. Each carries the thread and its agent, the new and the previous assignee, and who made the change.
{
"id": "evt_01J9X5H8R2",
"type": "thread.assigned",
"workspace_id": "wsk_01J9X0A1B2",
"agent_id": "agt_01J9X1C3V5",
"thread_id": "thrd_01J9X4B6T1",
"test_mode": false,
"data": {
"thread_id": "thrd_01J9X4B6T1",
"agent_id": "agt_01J9X1C3V5",
"assignee": { "id": "usr_01J9X2K7Q8", "email": "ada@acme.com", "name": "Ada Park" },
"previous_assignee": null,
"assigned_by": { "kind": "user", "id": "usr_01J9X3M2D4", "email": "sam@acme.com", "name": "Sam Lee" },
"thread": { "id": "thrd_01J9X4B6T1", "subject": "Refund for order 1041", "...": "the thread as it is now" }
},
"created_at": "2026-10-30T09:14:02.000Z"
}assigned_by is a person, { "kind": "user", "id", "email", "name" }, or an API key or connected app, { "kind": "key", "id" }. Once a person’s account is erased, their id, email and name are null in these events, and the events stay. Get the events on a webhook or from the event stream. A webhook endpoint receives them only when its event_types names them, as with the other thread events.
Routes
PATCH /Update a thread. Needs thev1/ threads/ {id} managescope.GET /List threads. Needs thev1/ threads readscope.GET /List members. Needs thev1/ members readscope.
Next steps
Full request and response shapes are in the API reference.
Was this page helpful?