{
  "name": "02 - Organise New Gmail with GPT-5.6 Luna",
  "nodes": [
    {
      "parameters": {
        "content": "## 2. Automatically label new messages\n\n**Event-driven stage.** Polls for newly received Inbox messages, loads only Gmail **user** labels, and asks GPT-5.6 Luna to choose one existing label. When no label fits, it returns an empty label ID and the message remains unchanged in Inbox. Existing system labels are preserved; nothing is archived, deleted, marked read, or unlabelled.",
        "height": 430,
        "width": 2920,
        "color": 5
      },
      "id": "s2-note",
      "name": "Stage 2 — Label new Gmail messages",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -160,
        -160
      ]
    },
    {
      "parameters": {
        "authentication": "oAuth2",
        "event": "messageReceived",
        "simple": true,
        "filters": {
          "includeSpamTrash": false,
          "includeDrafts": false,
          "q": "in:inbox -in:spam -in:trash",
          "readStatus": "both"
        },
        "options": {}
      },
      "id": "s2-trigger",
      "name": "Organise Gmail on receive",
      "type": "n8n-nodes-base.gmailTrigger",
      "typeVersion": 1.3,
      "position": [
        -80,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "const messages = $input.all().map(item => item.json);\nreturn [{ json: { messages } }];"
      },
      "id": "s2-bundle",
      "name": "Bundle New Gmail Messages",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        176,
        0
      ]
    },
    {
      "parameters": {
        "authentication": "oAuth2",
        "resource": "label",
        "operation": "getAll",
        "returnAll": true
      },
      "id": "s2-get-labels",
      "name": "Get Gmail User Labels (New)",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "position": [
        432,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "const messages = $('Bundle New Gmail Messages').first().json.messages || [];\nconst userLabels = $input.all().map(item => item.json).filter(label => label.type === 'user' && label.id && label.name);\nconst allowed = userLabels.map(label => ({ id: label.id, name: label.name }));\nreturn messages.map(message => ({ json: {\n  messageId: message.id,\n  allowedLabels: allowed,\n  prompt: [\n    'Classify this Gmail message into at most one existing user label.',\n    'Return ONLY valid JSON: {\"label\":\"exact existing label name or empty string\",\"reason\":\"short reason\"}.',\n    'Use only an exact name from ALLOWED_USER_LABELS. Never choose or invent a Gmail system label.',\n    'If no label clearly fits, return an empty label. Leaving the message unchanged in Inbox is correct.',\n    'Do not archive, delete, mark read, or remove any label.',\n    'ALLOWED_USER_LABELS: ' + JSON.stringify(allowed.map(label => label.name)),\n    'MESSAGE: ' + JSON.stringify({ from: message.From || message.from || '', subject: message.Subject || message.subject || '(no subject)', snippet: String(message.snippet || '').slice(0, 1200) }),\n  ].join('\\n'),\n} }));"
      },
      "id": "s2-build-prompts",
      "name": "Build New Gmail Message Prompts",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        704,
        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": "s2-ai",
      "name": "Classify New Gmail Message",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        992,
        0
      ]
    },
    {
      "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 responses = $input.all();\nconst sources = $('Build New Gmail Message Prompts').all();\nreturn responses.map((responseItem, index) => {\n  const paired = Array.isArray(responseItem.pairedItem) ? responseItem.pairedItem[0] : responseItem.pairedItem;\n  const sourceIndex = Number.isInteger(paired?.item) ? paired.item : index;\n  const source = sources[sourceIndex]?.json || sources[index]?.json || {};\n  const response = responseItem.json || {};\n  const parsed = parseStructured(response.output ?? response);\n  const requested = String(parsed.label || '').trim();\n  const reason = String(parsed.reason || '').trim();\n  const label = (source.allowedLabels || []).find(item => item.name.toLowerCase() === requested.toLowerCase());\n  return {\n    json: { messageId: source.messageId, labelId: label?.id || '', labelName: label?.name || '', reason },\n    pairedItem: { item: sourceIndex },\n  };\n});"
      },
      "id": "s2-match",
      "name": "Match Existing Gmail Label (New)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1264,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "4029787d-f109-43b2-8d22-e1ad61678556",
              "leftValue": "={{ $json.labelId }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "s2-if",
      "name": "Has Matching Gmail Label? (New)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1520,
        0
      ]
    },
    {
      "parameters": {
        "authentication": "oAuth2",
        "resource": "message",
        "operation": "addLabels",
        "messageId": "={{ $json.messageId }}",
        "labelIds": "={{ [$json.labelId] }}"
      },
      "id": "s2-add-label",
      "name": "Add Gmail Label to New Message",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "position": [
        1792,
        -64
      ]
    },
    {
      "parameters": {},
      "id": "s2-noop",
      "name": "Leave New Message in Inbox",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        1792,
        96
      ]
    },
    {
      "id": "s2-ai-request-builder",
      "name": "Build Classify New Gmail Message Request",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        768,
        0
      ],
      "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": {
    "Organise Gmail on receive": {
      "main": [
        [
          {
            "node": "Bundle New Gmail Messages",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Bundle New Gmail Messages": {
      "main": [
        [
          {
            "node": "Get Gmail User Labels (New)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Gmail User Labels (New)": {
      "main": [
        [
          {
            "node": "Build New Gmail Message Prompts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build New Gmail Message Prompts": {
      "main": [
        [
          {
            "node": "Build Classify New Gmail Message Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify New Gmail Message": {
      "main": [
        [
          {
            "node": "Match Existing Gmail Label (New)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Match Existing Gmail Label (New)": {
      "main": [
        [
          {
            "node": "Has Matching Gmail Label? (New)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Matching Gmail Label? (New)": {
      "main": [
        [
          {
            "node": "Add Gmail Label to New Message",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Leave New Message in Inbox",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Classify New Gmail Message Request": {
      "main": [
        [
          {
            "node": "Classify New Gmail Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "availableInMCP": false,
    "timezone": "Australia/Sydney"
  },
  "versionId": "9dc57ab9-1447-466c-a141-bd6afe6d9238",
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": []
}
