{
  "meta": {
    "templateId": "5727"
  },
  "name": "Categorize Gmail emails using GPT-4o-mini with Multi-Label Analysis",
  "tags": [],
  "nodes": [
    {
      "id": "gmail-trigger",
      "name": "Gmail Trigger",
      "type": "n8n-nodes-base.gmailTrigger",
      "position": [
        460,
        420
      ],
      "parameters": {
        "simple": false,
        "filters": {},
        "options": {},
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "get-gmail-labels",
      "name": "Get Gmail Labels",
      "type": "n8n-nodes-base.gmail",
      "position": [
        680,
        420
      ],
      "parameters": {
        "resource": "label",
        "returnAll": true
      },
      "typeVersion": 2.1
    },
    {
      "id": "get-email-content",
      "name": "Get Email Content",
      "type": "n8n-nodes-base.gmail",
      "position": [
        880,
        420
      ],
      "parameters": {
        "simple": false,
        "options": {},
        "messageId": "={{ $('Gmail Trigger').first().json.id }}",
        "operation": "get"
      },
      "typeVersion": 2.1
    },
    {
      "id": "prepare-prompt",
      "name": "Prepare Prompt",
      "type": "n8n-nodes-base.code",
      "position": [
        1100,
        420
      ],
      "parameters": {
        "jsCode": "// R\u00e9cup\u00e8re l'email et les labels\nconst emailData = $('Get Email Content').first().json;\nconst allLabels = $('Get Gmail Labels').all().map(item => item.json);\n\n// Filtre les labels syst\u00e8me\nconst systemLabels = ['INBOX', 'SENT', 'DRAFT', 'SPAM', 'TRASH', 'IMPORTANT', 'STARRED'];\nconst userLabels = allLabels.filter(label => \n  !systemLabels.includes(label.name.toUpperCase())\n);\n\n// Cr\u00e9er la liste des labels format\u00e9e\nconst labelsList = userLabels.map(label => `- ${label.name}`).join('\\n');\n\n// Pr\u00e9parer le contenu de l'email\nconst emailContent = emailData.text || emailData.body || emailData.snippet || 'Pas de contenu disponible';\n\n// Cr\u00e9er le prompt final MULTI-LABELS\nconst prompt = `Analyse cet email et choisis les MEILLEURS LABELS (1 \u00e0 3 maximum) parmi ceux disponibles :\n\nEMAIL \u00c0 ANALYSER :\nDe: ${emailData.from}\nObjet: ${emailData.subject}\nContenu: ${emailContent}\n\nLABELS DISPONIBLES :\n${labelsList}\n\nINSTRUCTIONS :\n1. Analyse le contenu et le contexte de l'email\n2. Choisis entre 1 et 3 labels les PLUS APPROPRI\u00c9S parmi la liste\n3. Privil\u00e9gie la qualit\u00e9 \u00e0 la quantit\u00e9 - si un seul label convient parfaitement, n'en mets qu'un\n4. \u00c9vite les labels syst\u00e8me comme INBOX, SENT, SPAM, etc.\n5. Classe les labels par ordre de pertinence (plus pertinent en premier)\n\nR\u00e9ponds en JSON avec la liste des labels choisis : {\"labels\": [\"label1\", \"label2\", \"label3\"]}`;\n\nconsole.log('\ud83d\udcdd Prompt multi-labels cr\u00e9\u00e9:', prompt);\nconsole.log('\ud83c\udff7\ufe0f Labels disponibles:', userLabels.map(l => l.name));\n\nreturn [{\n  json: {\n    prompt: prompt,\n    email: emailData,\n    labels: allLabels,\n    userLabels: userLabels\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "openai-chat-model",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        1340,
        600
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini"
        },
        "options": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "llm-chain",
      "name": "GPT\u20114o\u2011mini Label Analyzer",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        1340,
        420
      ],
      "parameters": {
        "text": "={{ $json.prompt }}",
        "promptType": "define"
      },
      "typeVersion": 1.5
    },
    {
      "id": "find-labels-id",
      "name": "Find Multi-Labels ID",
      "type": "n8n-nodes-base.code",
      "position": [
        1700,
        420
      ],
      "parameters": {
        "jsCode": "// Get LLM Chain response\nconst llmResponse = items[0].json;\n\n// Get labels from Prepare Prompt node\nconst preparePromptData = $('Prepare Prompt').first().json;\nconst allLabels = preparePromptData.labels || [];\n\nconsole.log('\ud83e\udd16 LLM Response Raw:', llmResponse);\nconsole.log('\ud83c\udff7\ufe0f All Labels:', allLabels.map(l => l.name));\n\n// Extract response content (different possible properties)\nlet responseText = '';\nif (llmResponse.text) {\n  responseText = llmResponse.text;\n} else if (llmResponse.response) {\n  responseText = llmResponse.response;\n} else if (llmResponse.output) {\n  responseText = llmResponse.output;\n} else if (typeof llmResponse === 'string') {\n  responseText = llmResponse;\n} else {\n  responseText = JSON.stringify(llmResponse);\n}\n\nconsole.log('\ud83d\udcdd Response Text:', responseText);\n\n// Extract label names from response\nlet labelNames = [];\n\n// Try to parse JSON first\ntry {\n  const parsed = JSON.parse(responseText);\n  if (parsed.labels && Array.isArray(parsed.labels)) {\n    labelNames = parsed.labels.map(label => label.trim()).filter(label => label.length > 0);\n  } else if (parsed.label) {\n    // Fallback for old single label format\n    labelNames = [parsed.label.trim()];\n  }\n} catch (e) {\n  // If not JSON, look for pattern {\"labels\": [...]}\n  const jsonMatch = responseText.match(/\\{[^}]*\"labels\"[^}]*\\[([^\\]]+)\\][^}]*\\}/);\n  if (jsonMatch) {\n    const labelsString = jsonMatch[1];\n    labelNames = labelsString.split(',').map(label => \n      label.replace(/[\"']/g, '').trim()\n    ).filter(label => label.length > 0);\n  } else {\n    // Fallback: look for old single label format\n    const singleMatch = responseText.match(/\\{[^}]*\"label\"[^}]*\"([^\"]+)\"[^}]*\\}/);\n    if (singleMatch) {\n      labelNames = [singleMatch[1].trim()];\n    } else {\n      // Last fallback: take first mentioned labels\n      for (const label of allLabels.slice(0, 3)) {\n        if (responseText.toLowerCase().includes(label.name.toLowerCase())) {\n          labelNames.push(label.name);\n        }\n      }\n    }\n  }\n}\n\n// Limit to 3 labels max\nlabelNames = labelNames.slice(0, 3);\n\nconsole.log('\ud83c\udfaf Extracted Label Names:', labelNames);\n\n// Find corresponding labels\nlet selectedLabels = [];\n\nfor (const labelName of labelNames) {\n  // Exact match\n  let foundLabel = allLabels.find(label => \n    label.name.toLowerCase() === labelName.toLowerCase()\n  );\n  \n  // Fallback: partial match\n  if (!foundLabel) {\n    foundLabel = allLabels.find(label => \n      label.name.toLowerCase().includes(labelName.toLowerCase()) ||\n      labelName.toLowerCase().includes(label.name.toLowerCase())\n    );\n  }\n  \n  if (foundLabel && !selectedLabels.find(l => l.id === foundLabel.id)) {\n    // Avoid duplicates\n    selectedLabels.push(foundLabel);\n  }\n}\n\n// If no label found, fallback to first non-system label\nif (selectedLabels.length === 0) {\n  const systemLabels = ['INBOX', 'SENT', 'DRAFT', 'SPAM', 'TRASH', 'IMPORTANT', 'STARRED'];\n  const fallbackLabel = allLabels.find(label => \n    !systemLabels.includes(label.name.toUpperCase())\n  ) || allLabels[0];\n  \n  if (fallbackLabel) {\n    selectedLabels = [fallbackLabel];\n  }\n}\n\nif (selectedLabels.length === 0) {\n  console.error('\u274c No labels found!');\n  return [{ json: { error: 'No labels found' } }];\n}\n\nconsole.log('\u2705 Final Selected Labels:', selectedLabels.map(l => `${l.name} (${l.id})`));\n\n// Return label IDs for application\nconst labelIds = selectedLabels.map(label => label.id);\n\nreturn [{\n  json: {\n    labelIds: labelIds,\n    labels: selectedLabels,\n    labelNames: selectedLabels.map(l => l.name),\n    llmResponse: responseText,\n    extractedLabelNames: labelNames,\n    count: selectedLabels.length\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "apply-multi-labels",
      "name": "Apply Multi-Labels",
      "type": "n8n-nodes-base.gmail",
      "position": [
        2060,
        420
      ],
      "parameters": {
        "labelIds": "={{ $json.labelIds }}",
        "messageId": "={{ $('Gmail Trigger').first().json.id }}",
        "operation": "addLabels"
      },
      "typeVersion": 2.1
    },
    {
      "id": "main-sticky-note",
      "name": "\ud83d\udccb USER GUIDE",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        380,
        720
      ],
      "parameters": {
        "width": 650,
        "height": 600,
        "content": "\ud83c\udfaf GMAIL ASSISTANT MULTI-LABELS (1-3 max)\n===========================================\n\n\ud83d\ude80 HOW TO USE THIS WORKFLOW:\n\n1\ufe0f\u20e3 ACTIVATION\n   \u2022 Click \"Activate\" button in top right\n   \u2022 Verify Gmail and OpenAI credentials are configured\n   \u2022 Workflow automatically monitors new emails\n\n2\ufe0f\u20e3 AUTOMATIC OPERATION\n   \u2022 As soon as new email arrives\n   \u2022 AI analyzes content (sender, subject, body)\n   \u2022 Smart selection of 1 to 3 relevant labels\n   \u2022 Automatic label application\n\n3\ufe0f\u20e3 ADVANTAGES\n   \u2022 Only 1 ChatGPT call per email (cost-effective)\n   \u2022 Up to 3 labels per email (more precise)\n   \u2022 Automatic adaptation to existing labels\n   \u2022 No configuration required\n\n\ud83d\udca1 BASED ON THE WORKING WORKFLOW\n   Stable and proven version adapted for multi-labels\n\n\ud83c\udf8a READY TO USE! Activate and let the magic happen!"
      },
      "typeVersion": 1
    },
    {
      "id": "trigger-sticky",
      "name": "\ud83d\udce7 TRIGGER",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        380,
        240
      ],
      "parameters": {
        "color": 2,
        "width": 660,
        "height": 480,
        "content": "\ud83d\udce7 EMAIL MONITORING\n\u2022 Checks every minute\n\u2022 Triggers on new emails\n\u2022 Retrieves message ID"
      },
      "typeVersion": 1
    },
    {
      "id": "ai-sticky",
      "name": "\ud83e\udd16 AI INTELLIGENCE",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1240,
        240
      ],
      "parameters": {
        "color": 3,
        "width": 430,
        "height": 480,
        "content": "\ud83e\udd16 AI MULTI-LABEL ANALYSIS\n\u2022 Optimized prompt for 1-3 labels\n\u2022 Cost-effective GPT-4o-mini\n\u2022 Structured JSON response\n\u2022 Smart contextual analysis"
      },
      "typeVersion": 1
    },
    {
      "id": "processing-sticky",
      "name": "\u2699\ufe0f PROCESSING",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1040,
        240
      ],
      "parameters": {
        "color": 4,
        "width": 200,
        "height": 480,
        "content": "\u2699\ufe0f DATA PREPARATION\n\u2022 Filters system labels\n\u2022 Creates multi-label prompt\n\u2022 Optimizes for AI\n\u2022 Provides full context"
      },
      "typeVersion": 1
    },
    {
      "id": "parsing-sticky",
      "name": "\ud83d\udd27 PARSING",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1660,
        240
      ],
      "parameters": {
        "color": 5,
        "width": 260,
        "height": 480,
        "content": "\ud83d\udd27 SMART PARSING\n\u2022 Parses JSON arrays [\"label1\", \"label2\"]\n\u2022 Fallback to single label format\n\u2022 Exact + partial matching\n\u2022 Avoids duplicates\n\u2022 Limits to 3 labels max"
      },
      "typeVersion": 1
    },
    {
      "id": "final-sticky",
      "name": "\u2705 APPLICATION",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1920,
        240
      ],
      "parameters": {
        "color": 6,
        "width": 360,
        "height": 480,
        "content": "\u2705 FINAL APPLICATION\n\u2022 Applies 1-3 selected labels\n\u2022 Uses labelIds array\n\u2022 Direct modification in Gmail\n\u2022 Automatic confirmation"
      },
      "typeVersion": 1
    },
    {
      "id": "tips-sticky",
      "name": "\ud83d\udca1 TIPS",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1040,
        880
      ],
      "parameters": {
        "color": 4,
        "width": 180,
        "height": 200,
        "content": "\ud83d\udca1 USAGE TIPS\n\u2022 Test with one email first\n\u2022 Check logs for debugging\n\u2022 Adjust prompt if necessary\n\u2022 Create specific labels before use"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "",
  "connections": {
    "LLM Chain": {
      "main": [
        [
          {
            "node": "Find Multi-Labels ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail Trigger": {
      "main": [
        [
          {
            "node": "Get Gmail Labels",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Prompt": {
      "main": [
        [
          {
            "node": "LLM Chain",
            "type": "main",
            "index": 0
          },
          {
            "node": "GPT\u20114o\u2011mini Label Analyzer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Gmail Labels": {
      "main": [
        [
          {
            "node": "Get Email Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Email Content": {
      "main": [
        [
          {
            "node": "Prepare Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "LLM Chain",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Find Multi-Labels ID": {
      "main": [
        [
          {
            "node": "Apply Multi-Labels",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GPT\u20114o\u2011mini Label Analyzer": {
      "main": [
        [
          {
            "node": "Find Multi-Labels ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}