Skip to main content

Assigning threads

Give each thread one owner on your team, from the dashboard or the API, and find what is yours.

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 });

Only 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));
{
  "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);

Search 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

Next steps

Full request and response shapes are in the API reference.

Was this page helpful?