bulk_modify
Bulk-apply label changes to messages matching a Gmail query. Supports add/remove labels, dry-run rehearsal, and verification for safe mass updates.
Instructions
Bulk-apply label changes to every message matching a Gmail query, batched at 1000 messages per API request. Labels may be given by name or by id: an unknown name in add is created automatically (use '/' for nested labels), an unknown name in remove is ignored. Returns matched/submitted counts, matched and submitted thread IDs (both lists capped at 500 — matchedThreadCount/submittedThreadCount hold the true totals), and per-chunk failures (partial success is reported, not hidden). IMPORTANT: submittedMessages is how many ids were handed to the API, NOT how many messages changed — messages.batchModify answers 204 with no body and ignores ids it does not recognise without a word, so an accepted request is not a performed one. Set verify:true to read the labels back afterwards and get verified {applied, notApplied[], unverifiable[]} — the only field here that reports an observed outcome. It costs one extra read per affected thread, so it is off by default; use it when a wrong 'done' would be acted on (trashing, or anything the user is told completed). If more messages match than maxMessages, only the first maxMessages are processed and 'capped' is true — raise maxMessages or re-run to finish the rest. NOTE: the query hits Gmail's search index as-is, WITHOUT the live re-verification search performs. The staleness that makes search re-verify was measured on threads.list (132 threads returned, 114 carrying no unread message at all); the same query through the message index this tool uses returned 19 hits, none stale — same mailbox, same minute. So the known drift does not reach this path, but that is one measurement, not a guarantee: unverifiedPredicates in the result names the conditions taken on the index's word, and when the outcome must be read-state-precise, resolve the set with search (which verifies against live labels) and act on those thread ids instead. Set crossCheck:true to ask Gmail the same question a second way before writing: each derived predicate is re-run as a label filter (labelIds) instead of a query operator, and any message the two routes disagree about is left untouched and listed in crossChecked.dropped. It costs one extra list per predicate — flat, not per message — so unlike verify it stays cheap on a large sweep. Read it as a contradiction detector: a disagreement is real, agreement proves nothing, because both routes read the same index. unverifiedPredicates therefore stays as it is even when this runs. A capped match set is not cross-checked at all (crossChecked.capped), since a message missing from a page is not a message missing the label. Set dryRun:true to rehearse: the same query resolution, matched counts/threads and the labels that would be created — and no message or label is touched. A dry run reads the SAME unverified index, so it confirms the size of the set, never its correctness. USE WHEN: mass operations — 'archive all newsletters older than 30 days' (query + remove INBOX), bulk labeling, bulk mark-read; dryRun first when the query is broad or the user should see the set before it changes. DO NOT USE: for a single thread (use modify_labels or the dedicated tools), or with neither add nor remove. SIDE EFFECTS: modifies up to maxMessages messages in one call (none with dryRun); label changes are reversible by the inverse call.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| add | No | ||
| query | Yes | ||
| dryRun | No | ||
| remove | No | ||
| verify | No | ||
| crossCheck | No | ||
| maxMessages | No |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| capped | Yes | ||
| dryRun | Yes | ||
| failed | Yes | ||
| verified | No | ||
| crossChecked | No | ||
| labelsToCreate | No | ||
| matchedThreads | Yes | ||
| matchedMessages | Yes | ||
| submittedThreads | Yes | ||
| submittedMessages | Yes | ||
| matchedThreadCount | Yes | ||
| submittedThreadCount | Yes | ||
| unverifiedPredicates | Yes |