{
  "id": "2eEiaDecIH2A1L5B",
  "meta": {
    "templateId": "2454",
    "templateCredsSetupCompleted": true
  },
  "name": "Auto-Categorize Outlook Emails with AI Agent",
  "tags": [
    {
      "id": "CbUjmxKVzwUyY7rS",
      "name": "metamation",
      "createdAt": "2025-02-27T02:44:26.211Z",
      "updatedAt": "2025-02-27T02:44:26.211Z"
    }
  ],
  "nodes": [
    {
      "id": "3269968d-0373-45a0-9e29-10c4859b6197",
      "name": "Microsoft Outlook Trigger",
      "type": "n8n-nodes-base.microsoftOutlookTrigger",
      "position": [
        320,
        272
      ],
      "parameters": {
        "fields": [
          "from",
          "subject",
          "isRead",
          "body"
        ],
        "output": "fields",
        "filters": {
          "foldersToInclude": [
            "AAMkAGRiZDlmMzhhLTRjOTctNGYxZC1iZDNjLTRhNTFkZGMzZTQ5YwAuAAAAAACdE2dovEwhS5uGZMOqP8rcAQCwdsIutyDWQrW17vFEit2HAAAAAAEMAAA="
          ]
        },
        "options": {},
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        }
      },
      "credentials": {
        "microsoftOutlookOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "3519328e-d6fa-482d-b2eb-8283d2e06bd7",
      "name": "AI Agent - Determine Category",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        960,
        272
      ],
      "parameters": {
        "text": "=Your goal is to keep the user's Inbox clean, containing ONLY actionable client communication or new inquiries. Categorise the email below by selecting the most appropriate folder ID from the list provided.\n\nEmail Subject: {{ $('Microsoft Outlook Trigger').item.json.subject }}\nSenders Email: {{ $('Microsoft Outlook Trigger').item.json.from.emailAddress.address }}\nSenders Name: {{ $('Microsoft Outlook Trigger').item.json.from.emailAddress.name }}\nEmail Body: {{ $json.sanitizedBody }}\nEmail ID: {{ $('Microsoft Outlook Trigger').item.json.id }}\n\n**Instructions:**\n1. First, determine if this email requires direct user action/reply (e.g., a client message, a new inquiry from a person). If YES, choose the ID for the 'Action' folder.\n2. If NO, determine the best category folder from the list provided (e.g., Junk, Receipt, SaaS, Community, Business, Other) and choose its ID.\n3. If none of the standard folders fit well, choose the ID for the 'Other' folder.\n4. Analyze sender, subject, and body.\n5. Emails that contain an ongoing conversation should not be moved.\n6. Use all tools to retrieve folders, contacts, and the ability to move messages, using ID's. \n7. Only ID of folders are recognized by outlook tools whening moving messages. \n8. Make sure to use the think tool. \n9. If a message is not moved, very briefly explain why. \n\nMessages from saved contacts should not be moved. \n\nIf the from field is an actual human, e.g. not a support@x.com, info@x.com, then do not use the move tool and simply output why it was not moved. You can also interpret this as if there is a persons name in the email.\ne.g. john@metamation.net.",
        "options": {
          "systemMessage": "You are an expert assistant for Alex helping manage his Outlook Inbox. The PRIMARY GOAL is to move all non-essential emails OUT of the Inbox."
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 1.6
    },
    {
      "id": "074a81c3-de13-455e-98c4-6b69f69830d0",
      "name": "Move Message",
      "type": "n8n-nodes-base.microsoftOutlookTool",
      "position": [
        992,
        496
      ],
      "parameters": {
        "folderId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Parent_Folder', `ID of the folder to move the message to`, 'string') }}"
        },
        "messageId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('Message', `ID of the email`, 'string') }}"
        },
        "operation": "move"
      },
      "credentials": {
        "microsoftOutlookOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "a18369cf-6ef2-4f90-ab20-c2d09350a06a",
      "name": "Get Folders",
      "type": "n8n-nodes-base.microsoftOutlookTool",
      "position": [
        1120,
        496
      ],
      "parameters": {
        "filters": {},
        "options": {},
        "resource": "folder",
        "operation": "getAll",
        "returnAll": true,
        "descriptionType": "manual",
        "toolDescription": "All folders available to move messages to. "
      },
      "credentials": {
        "microsoftOutlookOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "90c2c14d-5189-4793-9615-b83772185715",
      "name": "OpenRouter Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        864,
        496
      ],
      "parameters": {
        "model": "openai/gpt-4.1",
        "options": {}
      },
      "credentials": {
        "openRouterApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "137a6971-7823-4929-9034-2081774793af",
      "name": "Get Contacts",
      "type": "n8n-nodes-base.microsoftOutlookTool",
      "position": [
        1248,
        496
      ],
      "parameters": {
        "filters": {},
        "resource": "contact",
        "returnAll": true,
        "descriptionType": "manual",
        "toolDescription": "All saved contacts."
      },
      "credentials": {
        "microsoftOutlookOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "53a13b8f-1f87-498e-a386-56b0d13ee122",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -944,
        -176
      ],
      "parameters": {
        "width": 1200,
        "height": 1136,
        "content": "# Auto-Categorize Outlook Emails with AI in n8n\n\n## **How It Works**\n\n1. **Trigger:** The workflow starts with the **Microsoft Outlook Trigger** node, polling your inbox every minute for new emails.\n2. **Extract & Clean Email Content:** The email\u2019s key fields (from, subject, isRead, body) are extracted. The body is converted from HTML to Markdown, then sanitized to plain text for reliable AI processing.\n\n   **Node Setup Details:**\n\n   - **Microsoft Outlook Trigger**\n     - **Resource:** Message\n     - **Operation:** Trigger on new email\n     - **Fields to Output:** from, subject, isRead(optional), body\n     - **Folders to Include:** (Set to your Inbox or specific folder IDs)\n   \n   - **Markdown Node**\n     - **Input:** `{{$json[\"body\"][\"content\"]}}` (HTML email body)\n     - **Output Key:** `Email Body Markdown`\n     - **Purpose:** Converts HTML to Markdown for easier downstream processing.\n   \n   - **Sanitize Node (Code Node)**\n     - **Input:** `Email Body Markdown` from previous node\n     - **Purpose:** Cleans up Markdown, strips images, links, HTML tags, table formatting, and truncates to 4000 characters.\n     - **Sample JS Code:**\n       ```javascript\n       // Get the markdown content from the previous node\n       const markdownContent = $input.item.json[\"Email Body Markdown\"];\n\n3. **Setup AI tools**\n   - Move message and Get Folders Outlook tools are required, get contacts is optional.\n   - Set each field in the tools to \"defined automatically by the model\" and describe each field so the model understands how to use it.\n   - OpenRouter or other LLM models tool: You can use any client for this, but make sure to use a model that does well with tool calls (Claude, GPT-4.1, Gemini 2.5 Pro, etc.).\n\n---\n\n## **Best Practices & Notes**\n\n- **AI Prompt Engineering:** The AI is instructed to be conservative\u2014never move emails from real people or saved contacts, and always explain its reasoning if it doesn\u2019t move a message.\n- This automation only works for **NEW** incoming messages.\n- **Inbox Zero:** This system is designed to help you achieve and maintain Inbox Zero by keeping only actionable items in your main inbox.\n- **Customization:** You can adjust the folder logic, add more categories, or tweak the AI prompt for your specific needs.\n- **Privacy:** All processing happens within your n8n instance; no email data is stored outside your environment except for the AI call (which only receives sanitized, minimal content)."
      },
      "typeVersion": 1
    },
    {
      "id": "b9cf3998-4e0f-4eb3-b7d5-66f0095afdbc",
      "name": "Markdown",
      "type": "n8n-nodes-base.markdown",
      "position": [
        544,
        272
      ],
      "parameters": {
        "html": "={{ $json.body.content }}",
        "options": {},
        "destinationKey": "Email Body Markdown"
      },
      "typeVersion": 1
    },
    {
      "id": "1c6e14b7-9d17-4780-8e84-e5f047672366",
      "name": "Sanitize Email Body",
      "type": "n8n-nodes-base.code",
      "position": [
        768,
        272
      ],
      "parameters": {
        "jsCode": "// Get the markdown content from the previous node\nconst markdownContent = $input.item.json[\"Email Body Markdown\"];\n\nlet plainText = markdownContent;\n\n// 1. Remove Markdown images completely\nplainText = plainText.replace(/!\\[.*?\\]\\(.*?\\)/g, '');\n\n// 2. Simplify Markdown links - keep text, replace URL with '[link]'\nplainText = plainText.replace(/\\[(.*?)\\]\\(.*?\\)/g, '$1 [link]');\n// --- OR Remove [link] placeholder entirely if not needed ---\n// plainText = plainText.replace(/\\[(.*?)\\]\\(.*?\\)/g, '$1');\n\n// 3. Remove any remaining HTML-like tags\nplainText = plainText.replace(/<[^>]*>/g, '');\n\n// 4. Remove Markdown table separators and leading/trailing pipes from lines\nplainText = plainText.replace(/^\\||\\|$/gm, ''); // Remove leading/trailing pipes per line\nplainText = plainText.replace(/^\\s*[-|:]+\\s*$/gm, ''); // Remove lines that are just separators\n\n// 5. Normalize and reduce whitespace (run this AFTER other removals)\nplainText = plainText.replace(/(\\s*\\r?\\n\\s*){2,}/g, '\\n\\n'); // Collapse multiple blank lines\nplainText = plainText.split('\\n').map(line => line.trim()).filter(line => line.length > 0).join('\\n'); // Trim lines, remove empty lines\nplainText = plainText.trim(); // Trim whole string\n\n// 6. Truncate (Keep this step last)\nconst MAX_CHARS = 4000; // Or your desired limit\nif (plainText.length > MAX_CHARS) {\n  plainText = plainText.substring(0, MAX_CHARS) + \"... (truncated)\";\n}\n\n// Return the more thoroughly sanitized text\nreturn { sanitizedBody: plainText };"
      },
      "typeVersion": 2
    }
  ],
  "active": true,
  "settings": {},
  "versionId": "63068993-f77e-450a-9831-e227bb2637ae",
  "connections": {
    "Markdown": {
      "main": [
        [
          {
            "node": "Sanitize Email Body",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Folders": {
      "ai_tool": [
        [
          {
            "node": "AI Agent - Determine Category",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Get Contacts": {
      "ai_tool": [
        [
          {
            "node": "AI Agent - Determine Category",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Move Message": {
      "ai_tool": [
        [
          {
            "node": "AI Agent - Determine Category",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Sanitize Email Body": {
      "main": [
        [
          {
            "node": "AI Agent - Determine Category",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent - Determine Category",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Microsoft Outlook Trigger": {
      "main": [
        [
          {
            "node": "Markdown",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent - Determine Category": {
      "main": [
        []
      ]
    }
  }
}