bulk_move
Move filtered emails between iCloud mailboxes using copy-verify-delete with fingerprint verification. Preview changes with dry run mode before executing.
Instructions
Move emails matching any combination of filters from one mailbox to another. Uses safe copy-verify-delete with fingerprint verification and a persistent manifest. Use dryRun: true to preview without making changes.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| targetMailbox | Yes | Destination mailbox path | |
| sourceMailbox | No | Source mailbox (default INBOX) | |
| dryRun | No | If true, preview what would be moved without actually moving | |
| limit | No | Maximum number of emails to move (default: all matching) | |
| sender | No | Match exact sender email address | |
| domain | No | Match any sender from this domain (e.g. substack.com) | |
| subject | No | Keyword to match in subject | |
| before | No | Only emails before this date (YYYY-MM-DD) | |
| since | No | Only emails since this date (YYYY-MM-DD) | |
| unread | No | True for unread only, false for read only | |
| flagged | No | True for flagged only, false for unflagged only | |
| larger | No | Only emails larger than this size in KB | |
| smaller | No | Only emails smaller than this size in KB | |
| hasAttachment | No | Only emails with attachments (client-side BODYSTRUCTURE scan — must be combined with other filters that narrow results to under 500 emails first) | |
| account | No | Account name to use (e.g. 'icloud', 'gmail'). Defaults to first configured account. Use list_accounts to see available accounts. |
Implementation Reference
- lib/imap.js:1717-1742 (handler)The bulk_move function searches for emails matching the filters, ensures the target mailbox exists, and then performs a safe move operation using safeMoveEmails.
export async function bulkMove(filters, targetMailbox, sourceMailbox = 'INBOX', dryRun = false, limit = null, creds = null) { const client = createRateLimitedClient(creds); await client.connect(); await client.mailboxOpen(sourceMailbox); const query = buildQuery(filters); let uids = (await client.search(query, { uid: true })) ?? []; if (filters.hasAttachment) { if (uids.length > ATTACHMENT_SCAN_LIMIT) { await client.logout(); return { error: `hasAttachment requires narrower filters first — ${uids.length} candidates exceeds scan limit of ${ATTACHMENT_SCAN_LIMIT}.` }; } uids = await filterUidsByAttachment(client, uids); } await client.logout(); if (limit !== null) uids = uids.slice(0, limit); if (dryRun) { return { dryRun: true, wouldMove: uids.length, sourceMailbox, targetMailbox, filters }; } if (uids.length === 0) return { moved: 0, sourceMailbox, targetMailbox }; await ensureMailbox(targetMailbox, creds); const result = await safeMoveEmails(uids, sourceMailbox, targetMailbox, creds); return { ...result, sourceMailbox, targetMailbox, filters }; }