{
  "name": "02 - Organise New Emails with GPT-5.6 Luna",
  "active": false,
  "versionId": "02c31163-45a9-498c-8883-3c9ea565a7e1",
  "nodes": [
    {
      "parameters": {
        "jsCode": "// ============================================\n// ⚙️ SETTINGS - Edit to customize\n// ============================================\n//\n// HOW TO CHANGE:\n// - To disable something, add // at the start of the line\n// - To enable something, remove the // from the start\n// - Only ONE model should be uncommented at a time\n//\n// ============================================\n\n\n// 🤖 REPLICATE AI MODEL\n// Verified model and reasoning configuration for all workflow stages.\nconst MODEL = 'openai/gpt-5.6-luna';\nconst MODEL_LABEL = 'GPT-5.6 Luna';\nconst REASONING_EFFORT = 'none';\n\n\n// 🚫 FOLDER BLACKLIST\n// Folders the AI will NEVER sort into.\n// Remove a line to let the AI use that folder.\n\nconst FOLDER_BLACKLIST = [\n  'Inbox',\n  'Archive',\n  'Conversation History',\n  'Deleted Items',\n  'Drafts',\n  'Junk Email',\n  'Junk',\n  'Outbox',\n  'Sent Items',\n  'Notes',\n  'RSS Feeds',\n  'Sync Issues',\n  'Clutter',\n];\n\n\n// 📝 PROMPT\nconst PROMPT = `You are an email triage assistant. Given the available Outlook folders and an incoming email, decide which folder is most appropriate.\n\nAvailable folders: \\${folderNames}\n\nRespond with ONLY a JSON object, no other text:\n{\"folder\": \"<exact folder name>\", \"reason\": \"<1 sentence>\"}\n\nIf no folder is a good fit, use \"Inbox\" to leave it where it is.\n\nFrom: \\${from_name} <\\${from_email}>\nSubject: \\${subject}\nBody: \\${body}`;\n\n\n// 📏 MAX BODY LENGTH (characters)\nconst MAX_BODY = 300;\n\n\nreturn [{ json: {\n  model: MODEL,\n  modelLabel: MODEL_LABEL,\n  reasoningEffort: REASONING_EFFORT,\n  promptTemplate: PROMPT,\n  folderBlacklist: FOLDER_BLACKLIST,\n  maxBody: MAX_BODY\n}}];"
      },
      "id": "settings",
      "name": "⚙️ Settings",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        256,
        112
      ]
    },
    {
      "parameters": {
        "url": "https://graph.microsoft.com/v1.0/me/mailFolders?$top=50&$select=id,displayName",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "microsoftOutlookOAuth2Api",
        "options": {}
      },
      "id": "get-folders",
      "name": "Get Folders",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        256,
        272
      ]
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineAll",
        "options": {}
      },
      "id": "merge",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        512,
        192
      ]
    },
    {
      "parameters": {
        "jsCode": "const items = $input.all().map(i => i.json);\nconst settings = items.find(i => i.promptTemplate);\nconst folderData = items.find(i => i.value);\nconst msg = $('Organise on receive').first().json;\n\nconst blacklist = settings.folderBlacklist.map(f => f.toLowerCase());\nconst folders = (folderData.value || [])\n  .filter(f => !blacklist.includes(f.displayName.toLowerCase()))\n  .map(f => ({ id: f.id, name: f.displayName }));\nconst folderNames = folders.map(f => f.name).join(', ');\n\nconst from_email = msg.from || '';\nconst from_name = from_email.split('@')[0] || 'Unknown';\nconst subject = msg.subject || '(no subject)';\nconst body = (msg.bodyPreview || '').substring(0, settings.maxBody);\nconst prompt = eval('`' + settings.promptTemplate + '`');\n\nreturn [{ json: {\n  prompt,\n  folders: JSON.stringify(folders),\n  messageId: msg.id,\n  subject,\n  from: from_email,\n  model: settings.model,\n  modelLabel: settings.modelLabel,\n  reasoningEffort: settings.reasoningEffort\n}}];"
      },
      "id": "classify",
      "name": "Build New Email Prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        752,
        192
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 1
          },
          "conditions": [
            {
              "leftValue": "={{ $json.folderId }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "id": "267125ae-7c9f-4a06-9b71-5db717ce99f0"
            },
            {
              "leftValue": "={{ $json.folder }}",
              "rightValue": "Inbox",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "id": "bd36fe24-e57f-488c-810e-013cbe1bc6c9"
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "should-move",
      "name": "Should Move?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1520,
        192
      ]
    },
    {
      "parameters": {
        "operation": "move",
        "messageId": "={{ $json.messageId }}",
        "folderId": "={{ $json.folderId }}"
      },
      "id": "move-email",
      "name": "Move Email",
      "type": "n8n-nodes-base.microsoftOutlook",
      "typeVersion": 2,
      "position": [
        1776,
        112
      ]
    },
    {
      "parameters": {},
      "id": "noop-stay",
      "name": "Stay in Inbox",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        1776,
        288
      ]
    },
    {
      "parameters": {
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "filters": {},
        "options": {}
      },
      "id": "trigger",
      "name": "Organise on receive",
      "type": "n8n-nodes-base.microsoftOutlookTrigger",
      "typeVersion": 1,
      "position": [
        0,
        192
      ]
    },
    {
      "parameters": {
        "content": "## 2. Organize on receive\n\n**Event-driven stage.** Polls Outlook for a newly received message, loads allowed folders, classifies the email with Replicate `openai/gpt-5.6-luna` using **no reasoning**, and moves it only when an existing non-Inbox folder is confidently matched.\n\n### Setup after import\n\n1. Select your own Microsoft Outlook OAuth2 credential on every Outlook and Microsoft Graph node.\n2. Create or select a Generic Bearer Auth credential containing your Replicate API token on the GPT-5.6 Luna HTTP Request node.\n3. Review the Settings node and placeholders before activating or running this workflow.",
        "height": 448,
        "width": 2112,
        "color": 5
      },
      "id": "stage-note-organize-receive",
      "name": "Stage 2 — Organize on receive",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -112,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.replicate.com/v1/models/openai/gpt-5.6-luna/predictions",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBearerAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Prefer",
              "value": "wait=60"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ $json.replicateRequest }}",
        "options": {
          "timeout": 120000
        }
      },
      "id": "classify-new-email-http",
      "name": "Classify New Email (API)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1008,
        192
      ]
    },
    {
      "parameters": {
        "jsCode": "function parseStructured(value) {\n  if (value && typeof value === 'object' && !Array.isArray(value)) {\n    if (value.json_output && typeof value.json_output === 'object') return value.json_output;\n    if (value.output?.json_output && typeof value.output.json_output === 'object') return value.output.json_output;\n    if (typeof value.text !== 'string' && typeof value.output?.text !== 'string') return value;\n  }\n  const source = typeof value?.text === 'string'\n    ? value.text\n    : typeof value?.output?.text === 'string'\n      ? value.output.text\n      : value;\n  const text = Array.isArray(source)\n    ? source.map(part => typeof part === 'string' ? part : JSON.stringify(part)).join('')\n    : String(source ?? '');\n  try { return JSON.parse(text); } catch {}\n  const match = text.match(/\\{[\\s\\S]*\\}/);\n  if (!match) return {};\n  try { return JSON.parse(match[0]); } catch { return {}; }\n}\nconst response = $input.first().json;\nconst emailData = $('Build New Email Prompt').first().json;\nconst folders = JSON.parse(emailData.folders || '[]');\nconst parsed = parseStructured(response.output ?? response);\nconst match = folders.find(folder => folder.name.toLowerCase() === String(parsed.folder || '').toLowerCase());\nreturn [{ json: {\n  folder: String(parsed.folder || '_Unknown'),\n  reason: String(parsed.reason || 'No reason returned'),\n  folderId: match?.id || null,\n  messageId: emailData.messageId,\n  subject: emailData.subject,\n  from: emailData.from,\n  model: emailData.modelLabel\n} }];"
      },
      "id": "classify-new-email-parse",
      "name": "Parse New Email Classification",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1264,
        192
      ]
    },
    {
      "id": "classify-new-email-http-request-builder",
      "name": "Build Classify New Email (API) Request",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        784,
        192
      ],
      "parameters": {
        "jsCode": "return $input.all().map(item => {\n  const data = item.json;\n  const prompt = data.prompt;\n  return {\n    ...item,\n    json: {\n      ...data,\n      replicateRequest: {\n        input: {\n          prompt,\n          reasoning_effort: \"none\",\n          verbosity: 'low',\n          max_completion_tokens: 2000,\n        },\n      },\n    },\n  };\n});"
      }
    }
  ],
  "connections": {
    "⚙️ Settings": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Folders": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "Build New Email Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Should Move?": {
      "main": [
        [
          {
            "node": "Move Email",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Stay in Inbox",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Organise on receive": {
      "main": [
        [
          {
            "node": "Get Folders",
            "type": "main",
            "index": 0
          },
          {
            "node": "⚙️ Settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build New Email Prompt": {
      "main": [
        [
          {
            "node": "Build Classify New Email (API) Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify New Email (API)": {
      "main": [
        [
          {
            "node": "Parse New Email Classification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse New Email Classification": {
      "main": [
        [
          {
            "node": "Should Move?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Classify New Email (API) Request": {
      "main": [
        [
          {
            "node": "Classify New Email (API)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}
