Guide
Batch Modify Gmail Labels (API + CLI)
The Gmail API batchModify endpoint adds and removes labels on up to 1,000 messages in a single call. This guide shows the addLabelIds/removeLabelIds payload, the per-message modify fallback, and the cross-provider equivalent using the Nylas CLI mark and folders commands.
Written by Hazik Director of Product Management
Reviewed by Qasim Muhammad
What does the Gmail API batchModify endpoint do?
The Gmail API batchModify endpoint applies one label change to many messages in a single HTTP call. You pass an array of message IDs plus addLabelIds and removeLabelIds, and Gmail applies that delta to every listed message at once.
The endpoint accepts up to 1,000 message IDs per request and returns an empty 204 body on success — unlike users.messages.modify, it does not echo back the modified resources, per the users.messages.batchModify reference. The operation is also best-effort rather than atomic: if part of the batch fails, some messages may be modified and others left unchanged, so you re-run with the unmodified IDs.
POST https://gmail.googleapis.com/gmail/v1/users/me/messages/batchModify
Content-Type: application/json
{
"ids": ["18c1f2a3b4d5e6f7", "18c1f2a3b4d5e601", "18c1f2a3b4d5e602"],
"addLabelIds": ["Label_42"],
"removeLabelIds": ["UNREAD", "INBOX"]
}How do addLabelIds and removeLabelIds work together?
The Gmail API applies removeLabelIds and addLabelIds as a single delta per message: each value is a label ID, not a label name, and both arrays are optional. A label ID is either a system constant like INBOX or a generated string like Label_42.
Marking 200 messages read and filing them under a project label is two arrays in one call: remove UNREAD, add your project label ID. To archive, remove INBOX — Gmail has no separate archive flag; archiving is the absence of the INBOX label. Per the Gmail API labels guide, system labels are uppercase reserved IDs; user labels carry the Label_ prefix. Resolve a name to its ID once with the labels API before you batch.
# Same call, expressed as the delta it applies to every message:
# removeLabelIds: UNREAD -> mark read
# removeLabelIds: INBOX -> archive (no separate archive flag exists)
# addLabelIds: Label_42 -> file under your project label
{
"ids": ["18c1f2a3b4d5e6f7"],
"addLabelIds": ["Label_42"],
"removeLabelIds": ["UNREAD", "INBOX"]
}How do I batch label changes across providers with the CLI?
The Nylas CLI replaces Gmail-only label IDs with provider-neutral commands: nylas email mark for read and star state, and nylas email folders for the label or folder taxonomy. The same script works on Gmail, Microsoft Graph, IMAP, Yahoo, and iCloud without rewriting per backend.
There's no single CLI batch flag, so you fan out one call per message ID, which keeps each change individually retryable instead of failing a whole batch. The nylas email mark read command takes a message ID and clears the unread state — the cross-provider equivalent of removing the Gmail UNREAD label. The example below pulls 200 unread IDs and marks each one read.
# Mark up to 200 unread messages read, one retryable call per ID
nylas email list --unread --limit 200 --id --json \
| jq -r '.[].id' \
| while read -r MSG_ID; do
nylas email mark read "$MSG_ID"
doneFor the label or folder side, the nylas email folders list command returns every folder or Gmail label with its provider-native ID, so you resolve a name once before applying changes. On Gmail these map to labels; on Microsoft Graph they map to mail folders. List them with the --id flag to see the IDs you'll reference.
# List labels/folders with their IDs (Gmail labels, Graph folders, etc.)
nylas email folders list --id --jsonWhy does a 1,000-ID batch still hit rate limits?
A single batchModify call counts as more than one quota unit, so a 1,000-message batch is not free. Per the Gmail API usage limits, each project has a default ceiling of 1,200,000 quota units per minute and 250 quota units per user per second, and messages.batchModify costs 50 units per call.
When you exceed the per-user rate, Gmail returns 429 Too Many Requests with reason rateLimitExceeded. The fix is exponential backoff: retry after 1 second, then 2, then 4, capping at roughly 32 seconds plus jitter. The per-message CLI loop spreads requests naturally; a tight batch loop does not. See Gmail API error codes for the full 4xx and 5xx response table.
# Back off on 429 between batches instead of hammering the endpoint
for attempt in 1 2 4 8 16; do
if nylas email mark read "$MSG_ID"; then break; fi
sleep "$attempt"
doneWhat is the one Gmail label you can't see in the UI?
The label most batch jobs forget is UNREAD, a system label Gmail never shows as a clickable item in the sidebar. Read state in Gmail is just the presence or absence of this label, so “mark as read” is literally removing UNREAD from a message.
Forget it and your batch files messages under a project label but leaves them bold and unread, which defeats the triage. Gmail's reserved system label IDs include INBOX, SPAM, TRASH, STARRED, and IMPORTANT, all documented in the labels guide. With the CLI you sidestep the ID entirely: nylas email mark starred and nylas email mark read name the state directly, so there's no hidden label to remember.
# Star a message by intent, not by the STARRED label ID
nylas email mark starred "$MSG_ID"
# Mark read by intent, not by removing the UNREAD label ID
nylas email mark read "$MSG_ID"Next steps
- Gmail labels API — resolve a label name to its ID before you batch
- Gmail API batch delete — remove many messages at once with batchDelete
- Gmail API error codes — decode the 429 and 4xx responses a batch can return
- Route email to Jira issues — label and mark messages read after creating a ticket
- Route email to Zendesk tickets — triage support mail and file it once it's ticketed
- Command reference — every flag, subcommand, and example
- Gmail API: users.messages.batchModify — the reference for addLabelIds and removeLabelIds
- Gmail API: managing labels — system label IDs and the Label_ prefix for user labels
- Microsoft Graph: update message — the Graph equivalent for changing message state and folders