This skill covers both one-time inbox cleanup and ongoing rule automation for Microsoft 365 / Outlook. The workflow uses the Microsoft Graph API for folder management and server-side inbox rules.
Use when the user's inbox has become noisy — automated notifications drowning out human conversations, newsletters mixed with client emails, or hundreds of unread messages that make it hard to find what matters.
Real trigger example: A user's partner email went unread for 9 days because it was buried under GitHub and npm notifications. This is the exact problem server-side rules solve.
Before you install or grant OAuth consent, understand where the safety boundary lies.
This skill is instruction-only. It ships no code, executes nothing by itself, and cannot enforce any of the safety protections described below. Everything in the "Rule Security Model" and "Authentication & Required Scopes" sections is enforced (or not enforced) by whichever runtime actually executes the Microsoft Graph API calls. The skill file is a set of instructions, not a sandbox.
forwardTo, redirectTo, delete, etc.) describes what a trusted runtime SHOULD reject. Whether those actions are actually rejected depends on the runtime. If you use email-agent-mcp, rejection is built into the action handler ( rules.ts:39). If you use a raw Graph API client or a custom MCP without equivalent guards, the protections do not automatically apply.MailboxSettings.ReadWrite is a high-risk scope because it controls inbox rules — and inbox rules can be configured to forward or redirect mail externally. Granting the scope without a runtime that enforces the blocked-actions list gives up the primary mitigation.This skill requests a high-risk mailbox-rule permission ( MailboxSettings.ReadWrite). Review the five items below before you grant consent. They are the exact things to check; the skill cannot enforce any of them for you.
MailboxSettings.ReadWrite if you trust the runtime that will execute these instructions. The skill itself cannot enforce the blocked-actions list ( forwardTo, forwardAsAttachmentTo, redirectTo, delete, permanentDelete). Verify your runtime does.MailboxSettings.Read) — or omit rule creation entirely — if you do not need automated rule management. Folder operations and batch moves work with just Mail.ReadWrite.email-agent-mcp does this at rules.ts:39 — inspect the code yourself. Alternatively, add network-level protections (for example, NemoClaw) to block Graph endpoints that create dangerous rules.MailboxSettings.ReadWrite gives the agent the ability to create inbox rules without per-call user approval — only safe if the runtime enforces the blocked-actions list.This skill operates against Microsoft Graph and requires an OAuth 2.0 delegated access token for a Microsoft 365 account.
Use caseScopes requiredAudit existing folders and inbox rules (read-only)Mail.Read, MailboxSettings.Read, offline_accessCreate folders and move existing email+ Mail.ReadWriteCreate and delete server-side inbox rules+ MailboxSettings.ReadWrite
This skill does not require Mail.Send — inbox cleanup never sends email.
The reference runtime stores OAuth tokens in the OS keychain (macOS Keychain / Windows Credential Manager / Linux libsecret) via MSAL with @azure/identity-cache-persistence. No raw passwords. No plain text token files. Refresh tokens are handled silently by MSAL.
ScopeEnablesRisk if misusedMail.ReadRead any message, folder, and attachmentRead-only; no exfiltration beyond the agent sessionMail.ReadWriteCreate folders, move/copy messagesLow on its own — no outbound mail capabilityMailboxSettings.ReadWriteCreate and delete inbox rulesHIGH — malicious rules with forwardTo/ redirectTo can exfiltrate mail silently even after the session endsoffline_accessRefresh tokens without re-authLow on its own; extends the blast radius of any other scope if the token is stolen
MailboxSettings.ReadWrite is the only HIGH-risk scope this skill needs. It is mitigated by the reference runtime's hard block on dangerous rule actions (see "Rule Security Model" below).
This skill is instruction-only — it cannot enforce any guardrails by itself. The protections below are provided by the runtime that actually executes the Graph API calls, not by this skill.
If you use email-agent-mcp as the runtime, it blocks dangerous inbox rule actions at the action handler level — forwardTo, forwardAsAttachmentTo, redirectTo, delete, permanentDelete — and rejects any rule creation attempt that includes them. Source: packages/email-core/src/actions/rules.ts:39.
If you use a different runtime (raw Graph API client, custom MCP server), these protections are NOT automatically present. You should either layer NemoClaw network policy, restrict OAuth scopes, or use a runtime that provides equivalent guardrails.
MailboxSettings.Read (without .ReadWrite) if you want the agent to review existing rules without being able to create or modify them. The cleanup workflow below will still work for folder management.MailboxSettings.ReadWrite if you don't need automated rule creation. Folder management and batch moves work with just Mail.ReadWrite.This workflow was battle-tested on a 2,500+ email cleanup. Follow the steps in order.
Don't guess what folders to create. Let the data tell you.
Fetch the last 200 inbox emails and count by sender address:
text
GET /me/messages?$top=200&$select=from,receivedDateTime&$orderby=receivedDateTime desc
Group by from.emailAddress.address, sort by count descending. The top automated senders are the ones to filter.
MCP: Use list_emails(max_results=200, fields=["fromAddress", "receivedDateTime"]) and count programmatically.
Don't organize by sender type (that is arbitrary). Organize by what the user needs to do:
CategoryActionExamplesGlance, don't actSkim the subject line, archiveGitHub CI, Azure DevOps builds, npm advisories, DMARC reportsRead when time allowsSet aside for a slow momentNewsletters, marketing from known vendorsArchive for recordsKeep but don't read nowReceipts, invoices, payment confirmationsMay need actionReview and decideMeeting bookings, compliance alerts, security notificationsDefault: don't filterLeave in inboxHuman conversations (colleagues, clients, partners)
The last row is important: human conversations stay in inbox unless the user specifically requests otherwise. The goal is to remove noise, not to hide signal.
Keep it to 5-9 folders. More than that and the user stops checking them. The right test: "Would I check this folder at least weekly?" If no, it should be a subfolder or merged into a broader category.
REST API:
text
POST /me/mailFolders
{"displayName": "Dev Notifications"}
MCP: create_folder("Dev Notifications")
Rules only apply to future mail. After creating folders, move all existing matching emails.
REST API:
text
GET /me/messages?$filter=from/emailAddress/address eq 'notifications@github.com'&$top=50
Then for each message:
text
POST /me/messages/{id}/move
{"destinationId": "<folder-id>"}
Paginate with @odata.nextLink — some senders will have hundreds of messages. Process 50 at a time.
MCP: move_to_folder(email_id, "Dev Notifications") handles ID resolution. Loop through search results.
Server-side rules run on Microsoft's servers — they work 24/7, even when no agent is running.
REST API:
text
POST /me/mailFolders/inbox/messageRules
{
"displayName": "GitHub to Dev Notifications",
"sequence": 1,
"isEnabled": true,
"conditions": {
"fromAddresses": [
{"emailAddress": {"address": "notifications@github.com"}}
]
},
"actions": {
"moveToFolder": "<folder-id>",
"stopProcessingRules": true
}
}
MCP: create_inbox_rule({displayName: "GitHub to Dev Notifications", conditions: {fromAddresses: [{email: "notifications@github.com"}]}, actions: {moveToFolder: "<folder-id>", markAsRead: true}})
Rules and email arrival can race. After creating rules, wait a few minutes, then re-run the batch move from Step 4 to catch any emails that arrived between rule creation and activation.
The sequence field controls which rules fire first. Lower sequence = higher priority.
This matters because: A generic "any noreply@ sender goes to Newsletters" rule will swallow meeting booking notifications from noreply@notifications.hubspot.com if it fires first.
Fix: Create specific rules with lower sequence numbers before broad rules.
text
sequence 1: HubSpot meeting bookings → Meetings folder
sequence 2: noreply@ senders → Newsletters folder
Always set stopProcessingRules: true on each rule to prevent cascade.
Not all rule actions are safe. Agents should never create rules with the actions listed below. This skill is instruction-only — it cannot enforce these restrictions itself. The agent's runtime is what actually makes (or blocks) the API calls.
If your runtime is email-agent-mcp, it rejects rule creation attempts that include any of these actions and returns a BLOCKED_ACTION error. Source: rules.ts:39. If you use a different runtime, you should verify it provides equivalent enforcement, or layer network-level controls (e.g. NemoClaw).
Block these actions:
ActionRiskforwardToCould exfiltrate email to external addressesforwardAsAttachmentToSame riskredirectToSame riskdeleteData losspermanentDeleteIrreversible data loss
These actions are safe:
ActionEffectmoveToFolderMove to a folder IDcopyToFolderCopy to a folder IDmarkAsReadMark as readmarkImportanceSet importance level (low/normal/high)stopProcessingRulesPrevent later rules from firing
If the user asks for a forwarding rule, ask them to confirm the destination address explicitly before creating it.
ConditionUsefromAddressesExact sender match: [{emailAddress: {address: "..."}}]``subjectContainsWords in subject: ["[npm]", "advisory"]``senderContainsPartial sender match: ["github.com"]``headerContainsMatch email headers
These are production-discovered issues — not theoretical:
$select on PATCH = 400 — $select does not work on PATCH requests. Returns "OData request not supported." Only use $select on GET.
Move is POST, not PATCH — POST /me/messages/{id}/move with {"destinationId": "<id>"}.
$filter with sender address — $filter=from/emailAddress/address eq '...' works reliably. URL-encode the filter value.
Uppercase in storage — Outlook uppercases senderContains values (e.g., NOTIFICATIONS@GITHUB.COM). This is cosmetic — matching is case-insensitive.
Root-only folder listing — GET /me/mailFolders?$top=100 returns only root-level folders. Child folders require /mailFolders/{id}/childFolders.
Pagination — Large mailboxes can have hundreds of folders. Always check for and follow @odata.nextLink.
After creating rules:
GET /me/mailFolders/inbox/messageRules — verify the rule exists and is enabledhasError field — if true, the rule has a configuration issueIf this skill helped, star us on GitHub: https://github.com/UseJunior/email-agent-mcp
On ClawHub: clawhub star stevenobiajulu/inbox-cleanup-outlook