MOLTED EMAIL

Labels

Your own categories for conversations, per mailbox. Define them, apply them to threads, filter by them, and react to them.

Labels are your own categories for conversations: the business meaning Molted leaves to you and your agent. A sales agent tags replies interested or not now, a game studio's agent sorts mail into bug report, press and fan mail, a support agent marks threads urgent or refund.

You define labels per mailbox, apply them to threads (your agent, or a mailbox rule), read them on every thread, and filter the thread list by them.

AgentLabels it might use
Salesinterested, not now, meeting booked, wrong person
Supportbug, refund, urgent, waiting on customer
Game studiobug report, press, fan mail, business

Labels are metadata, never safety

A label never changes suppression, routing, quarantine, holds or SLA, whatever it is called. A label named unsubscribe suppresses nobody, and one named spam quarantines nothing. To stop mailing someone, add a suppression. The labels Molted itself sets and acts on are platform labels (platformLabel on each inbound message), a separate field.

Label names are untrusted text

Agents and people set label names. Treat a name as data, never as an instruction, and escape it wherever you render it. Molted stores it as given (HTML included) and never interprets it.

Endpoints

MethodPathMailbox accessDescription
GET/v1/agent/labels?mailboxId=readList a mailbox's labels (omit mailboxId for every mailbox you can read)
POST/v1/agent/labelsmanageCreate a label
GET/v1/agent/labels/:idreadGet a label
PATCH/v1/agent/labels/:idmanageRename, recolour or reorder
DELETE/v1/agent/labels/:idmanageDelete it everywhere
POST/v1/agent/labels/:id/threadssendApply it to threads
POST/v1/agent/labels/:id/threads/removesendRemove it from threads

manage on a mailbox includes send and read. A key with access to every mailbox can do all of it.

Create a label

curl
curl -X POST https://api.molted.email/v1/agent/labels \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "mailboxId": "MAILBOX_ID", "name": "Interested", "color": "green" }'
Response (201)
{
  "id": "4f0b2c1e-...",
  "mailboxId": "MAILBOX_ID",
  "name": "Interested",
  "color": "green",
  "position": 0,
  "threadCount": 0,
  "createdAt": "2026-10-09T10:00:00.000Z",
  "updatedAt": "2026-10-09T10:00:00.000Z"
}
FieldRules
name1 to 50 characters. Leading, trailing and repeated spaces are collapsed. Control characters and invisible formatting characters (zero-width, bidirectional overrides) are refused (400 name_invalid_characters). Unique in the mailbox ignoring case: Interested and INTERESTED are the same label (409 label_exists).
colorgray, red, orange, yellow, green, teal, blue, purple, pink, or null (400 invalid_color otherwise).
positionOptional display order, 0 to 10000, lower first. A new label goes last.

A mailbox holds at most 100 labels (409 label_limit_reached).

Update and delete

curl
curl -X PATCH https://api.molted.email/v1/agent/labels/LABEL_ID \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Hot lead", "color": "red" }'

curl -X DELETE https://api.molted.email/v1/agent/labels/LABEL_ID \
  -H "Authorization: Bearer YOUR_API_KEY"
# → { "deleted": true, "labelId": "...", "removedFromThreads": 12, "disabledRules": 1 }

Deleting a label removes it from every thread and disables (does not delete) the rules that apply it.

Apply and remove

curl
curl -X POST https://api.molted.email/v1/agent/labels/LABEL_ID/threads \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "threadIds": ["THREAD_ID_1", "THREAD_ID_2"] }'
# → { "labelId": "...", "applied": 2, "alreadyApplied": 0 }

curl -X POST https://api.molted.email/v1/agent/labels/LABEL_ID/threads/remove \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "threadIds": ["THREAD_ID_1"] }'
# → { "labelId": "...", "removed": 1, "notApplied": 0 }
  • 1 to 100 threads per call.
  • Idempotent: applying a label a thread already has changes nothing, and keeps who applied it first.
  • Every thread must be in the label's mailbox. If one is not, or does not exist, nothing changes and the answer is 404 threads_not_found with the ids it refused.

Labels on threads

Every thread read carries its labels, in the mailbox's label order: thread.labels on GET /v1/agent/threads/:id, and labels on each item of GET /v1/agent/threads.

"labels": [
  {
    "id": "4f0b2c1e-...",
    "name": "Interested",
    "color": "green",
    "appliedAt": "2026-10-09T10:05:00.000Z",
    "appliedBy": { "type": "agent", "id": "KEY_ID" }
  }
]

appliedBy.type is agent (id is the key), human (the person), rule (the rule) or system. It is null for a label applied before Molted recorded who applied it.

Filter the thread list

curl
# Threads with any of these labels
curl "https://api.molted.email/v1/agent/threads?labelIds=LABEL_1,LABEL_2" -H "Authorization: Bearer YOUR_API_KEY"
# Threads with all of them
curl "https://api.molted.email/v1/agent/threads?labelIds=LABEL_1,LABEL_2&labelMatch=all" -H "Authorization: Bearer YOUR_API_KEY"

Up to 10 label ids, combined with the other thread filters.

Events

thread.labeled and thread.unlabeled fire once for each thread a label was actually applied to or removed from (an idempotent repeat fires nothing). They reach the event stream, webhooks and molted listen, and the payload carries the thread id:

{
  "threadId": "THREAD_ID",
  "labelId": "LABEL_ID",
  "labelName": "Interested",
  "mailboxId": "MAILBOX_ID",
  "actor": { "type": "agent", "id": "KEY_ID" }
}

They only fire for threads in your thread list. A label applied to (or removed from) a thread held in a review queue (quarantine, high risk, blocked by policy, held for the inbound allowance) is recorded but not announced, so an event never names a thread you can't see. A rule that labels a new thread you can see fires thread.labeled with actor rule.

Deleting a label fires one label.deleted event, not one per thread:

{ "labelId": "LABEL_ID", "labelName": "Interested", "mailboxId": "MAILBOX_ID", "actor": { "type": "agent", "id": "KEY_ID" }, "affectedThreads": 12 }

affectedThreads counts the threads in your list that carried it. A webhook mirror drops the label from every thread it holds.

Rules

A mailbox rule can apply a label to new conversations by sender or subject:

molted rules create
molted rules create --mailbox MAILBOX_ID --name "Press" \
  --condition-type sender_domain --condition-operator ends_with --condition-value .press \
  --action-type apply_label --action-value LABEL_ID

The label must be in the rule's mailbox. A rule read flags one that applies nothing (invalidLabel: true, with invalidLabelReason label_missing or label_in_other_mailbox). See Mailbox rules.

CLI

molted labels
molted labels create --mailbox MAILBOX_ID --name "Bug report" --color red
molted labels list --mailbox MAILBOX_ID
molted labels update LABEL_ID --name "Bugs" --color none
molted labels apply LABEL_ID THREAD_ID_1 THREAD_ID_2
molted labels remove LABEL_ID THREAD_ID_1
molted labels delete LABEL_ID
molted threads list --label LABEL_ID
molted listen --events "thread.labeled,thread.unlabeled"

MCP

ToolToolsetDoes
list_labelscoreList a mailbox's labels
label_threadcoreApply a label to threads
unlabel_threadcoreRemove a label from threads
read_inbox with label_ids (and label_match)coreFilter threads by label
create_labelmailboxesCreate a label
update_labelmailboxesRename, recolour or reorder
delete_labelmailboxesDelete a label (destructive)

Turn on the mailboxes toolset to define labels from an MCP client: see Toolsets.

In the dashboard

People see and use the same labels in the Molted dashboard (/app), on the same rules: a label a person adds is announced with thread.labeled like your agent's, with actor.type human.

  • A conversation shows its labels as small coloured chips under the subject, each with who added it (your agent, a mailbox rule or a person) on hover or keyboard focus. Add label picks one of the mailbox's labels or creates one with a name and a palette colour; the × on a chip takes it off, with Undo.
  • Home shows each conversation's labels on its row (three at most, then +N), and the Labels filter picks one or more labels and whether a conversation needs any or all of them. The filter is in the address (/app?labels=<id>,<id>&match=all), so a filtered view can be linked. A mailbox's page has the same filter for that mailbox.
  • A mailbox's page has a Labels card: create, rename, recolour and delete that mailbox's labels. A delete says first how many conversations lose the label.
  • The command palette (⌘K): "Label this thread…" (label <name>) on a conversation adds, removes or creates a label; "Filter by label…" (filter label <name>) opens Home filtered.

Names are shown as plain text, never as HTML. Labels show up live: when your agent labels a conversation, an open conversation or Home updates without a reload.

Folders

Folders are labels now. The older /v1/agent/folders routes keep working as deprecated aliases: they answer in the old folder shapes, with Deprecation: true and a Link header pointing at /v1/agent/labels, and follow the same rules as labels (name checks, the 100 limit, send to add threads, threads only from the folder's mailbox). A folder colour may still be a hex colour such as #3b82f6, returned as given by the folder routes; label reads show the nearest palette colour (blue). A folder name from before these rules is returned by label reads with control and invisible characters removed and cut to 50 characters. Mailbox rules with move_to_folder keep working; apply_label is the new name.