Folders and labels
Mail is grouped into threads, and every thread sits in one folder and can carry labels. Agents use them the way people use a mailbox: file what is done, flag what needs a person, and find it again later.
Folders
inbox, archive, spam, trash or sent. archive goes with status archived. sent holds a thread whose messages are all ones the agent sent, until a reply moves it to the inbox. Threads in trash are deleted for good 30 days after they were trashed.
Move a thread with PATCH /v1/threads/{id}: Move the thread to inbox, archive, spam or trash. archive also sets status "archived", and moving an archived thread anywhere else reopens it. sent cannot be chosen (422): a thread is filed there while every message in it is one you sent. A thread in trash is deleted for good 30 days after it was moved there.
# File a thread in archive and mark it read
curl -X PATCH "https://api.mails.ai/v1/threads/$THREAD_ID" \
-H "Authorization: Bearer $MAILS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "folder": "archive", "read": true }'Read and unread
A thread’s read flag turns false when new mail arrives; set it with PATCH /v1/threads/{id}. GET /v1/agents/{id}/inbox gives an agent’s unread count and the thread and unread counts of each folder (inbox, archive, spam, trash, sent), and the counts of each label among its inbox threads.
Trash and deleting
Moves the thread to trash (deleted: false, the thread returned). Sent again for a thread already in trash it changes nothing, so a retried request never destroys mail. With permanent=true it deletes the thread for good, from any folder: its messages' content, raw email and attachments are removed, and thread.deleted is emitted. Trash is also emptied automatically 30 days after a thread was moved there. A test key cannot trash or delete a thread holding live mail (403 live_mode_required). An app connected by sign-in may move a thread to trash but not delete it for good: with permanent=true it gets 403 connected_app_not_allowed.
# Move a thread to trash
curl -X DELETE "https://api.mails.ai/v1/threads/$THREAD_ID" \
-H "Authorization: Bearer $MAILS_API_KEY"Labels
A label is a name and a colour, applied to threads. A name (unique in the workspace, ignoring case) and a colour from a fixed palette of 12. Every label in the workspace (at most 200), by name.
| Field | Description |
|---|---|
id | string Required. Label id (lbl_…). |
name | string Required. Unique in the workspace, ignoring case. |
color | string Required. The label's colour, one of 12 palette names: gray unless another was chosen. One of: gray, red, orange, amber, yellow, green, teal, cyan, blue, indigo, purple, pink. |
created_at | string Required. When the label was created (UTC). |
updated_at | string Required. When the label was last changed with PATCH, even by a request that left it as it was. Equal to created_at on a label never patched. |
Put labels on a thread with PATCH /v1/threads/{id}. Renaming a label renames it on every thread that carries it. Deletes the label and takes it off every thread that carries it.
| Field | Description |
|---|---|
labels | string[] Label names, up to 50, each at most 64 characters. Replaces the thread's labels. Any such name works; a name that matches a label object picks up its id and colour. |
label_ids | string[] Label object ids (lbl_…). Replaces the thread's labels with those labels' names. Sent together with labels, the thread gets both. |
# Create a label
curl -X POST https://api.mails.ai/v1/labels \
-H "Authorization: Bearer $MAILS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Needs a person", "color": "red" }'# Put it on a thread (this replaces the thread's labels)
curl -X PATCH "https://api.mails.ai/v1/threads/$THREAD_ID" \
-H "Authorization: Bearer $MAILS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "labels": ["Needs a person"] }'import { createClient } from "@mailsai/sdk";
const client = createClient(); // reads MAILS_API_KEY
const { data } = await client.threads.list({ limit: 1 });
// Replaces the thread's labels
await client.threads.update(data[0].id, { labels: ["Needs a person"] });Find threads by folder and label
GET /v1/threads takes these filters, and GET /v1/search takes the same folder and label ones:
| Parameter | Description |
|---|---|
folder | string Which folder's threads to return, or all for every folder. Without it: every folder except trash. One of: inbox, archive, spam, trash, sent, all. |
read | boolean false for unread threads only, true for read ones. |
label | string Only threads carrying this label (a name, or a label id lbl_…). |
status | string Filter by thread status (open|closed|archived). |
# Unread threads labelled "Needs a person", in every folder but trash
curl -G "https://api.mails.ai/v1/threads" \
-H "Authorization: Bearer $MAILS_API_KEY" \
--data-urlencode "label=Needs a person" \
--data-urlencode "read=false"Thread events
PATCH /v1/threads/{id} emits thread.updated when something changed, and deleting a thread for good emits thread.deleted. On a webhook endpoint: Events to deliver, at least one. "*" is every event except thread.created, thread.updated, thread.deleted and message.delayed, sent only to an endpoint that names them.
Routes
GET /List threads. Needs thev1/ threads readscope.GET /Retrieve a thread with messages. Needs thev1/ threads/ {id} readscope.PATCH /Update a thread. Needs thev1/ threads/ {id} managescope.DELETE /Trash or delete a thread. Needs thev1/ threads/ {id} managescope.GET /Inbox summary. Needs thev1/ agents/ {id}/ inbox readscope.GET /List labels. Needs thev1/ labels readscope.POST /Create a label. Needs thev1/ labels managescope.GET /Retrieve a label. Needs thev1/ labels/ {id} readscope.PATCH /Update a label. Needs thev1/ labels/ {id} managescope.DELETE /Delete a label. Needs thev1/ labels/ {id} managescope.
Next steps
Full request and response shapes are in the API reference.
Was this page helpful?