AutomationFlowsAI & RAG › Gmail AI Agent for Auto Classification

Gmail AI Agent for Auto Classification

Original n8n title: Gmail AI Agent - 자동 분류 및 처리

Gmail AI Agent - 자동 분류 및 처리. Uses httpRequest, agent, n8n-nodes-upstage. Webhook trigger; 26 nodes.

Webhook trigger★★★★☆ complexityAI-powered26 nodesHTTP RequestAgentN8N Nodes Upstage
AI & RAG Trigger: Webhook Nodes: 26 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow follows the Agent → HTTP Request recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "name": "Gmail AI Agent - \uc790\ub3d9 \ubd84\ub958 \ubc0f \ucc98\ub9ac",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "gmail-list-extended",
        "responseMode": "lastNode",
        "options": {}
      },
      "id": "6517fa0c-240d-4093-8ac7-3f58681d37b5",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1,
      "position": [
        800,
        1200
      ]
    },
    {
      "parameters": {},
      "id": "c4cd49e3-1198-4b11-9b41-93b1034fc973",
      "name": "MergeTriggers",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 1,
      "position": [
        1152,
        1216
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const DEBUG = $env.DEBUG_N8N === 'true';\nconst item = $input.item;\nconst incomingToken = item.json?.body?.accessToken || item.json?.accessToken || '';\nconst maxResults = item.json?.body?.max || item.json?.max || 50;\nconst query = item.json?.body?.query || item.json?.query || '';\nconst resetStaticData = item.json?.body?.resetStaticData || item.json?.resetStaticData || false;\n\nif (DEBUG) {\n  console.log('[CheckToken] Incoming data:', {\n    hasToken: !!incomingToken,\n    tokenLength: incomingToken.length,\n    maxResults,\n    query,\n    resetStaticData\n  });\n}\n\nif (incomingToken && incomingToken.trim() !== '') {\n  return {\n    json: {\n      accessToken: incomingToken.trim(),\n      maxResults: maxResults,\n      query: query,\n      resetStaticData: resetStaticData,\n      needsToken: false,\n      tokenSource: 'webhook'\n    }\n  };\n}\n\nreturn {\n  json: {\n    maxResults: maxResults,\n    query: query,\n    resetStaticData: resetStaticData,\n    needsToken: true,\n    tokenSource: 'schedule'\n  }\n};"
      },
      "id": "4ebdc7bf-1c7b-453f-bb6b-736c2aa7278f",
      "name": "CheckToken",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1376,
        1216
      ]
    },
    {
      "parameters": {
        "url": "=http://host.docker.internal:3000/api/gmail/token",
        "options": {},
        "headerParametersUi": {
          "parameter": [
            {
              "name": "Authorization",
              "value": "=Bearer {{$env.N8N_API_KEY || ''}}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "id": "66295976-a7c8-4576-bd89-a6d96c121a8e",
      "name": "GetTokenFromNextJS",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        1584,
        1472
      ],
      "continueOnFail": true,
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const DEBUG = $env.DEBUG_N8N === 'true';\nconst item = $input.item;\nconst error = item.json?.error || item.json?.message || 'Unknown error';\nconst statusCode = item.json?.statusCode || item.json?.status || 'N/A';\nconst response = item.json?.response || item.json || {};\n\nconst errorDetails = {\n  node: 'GetTokenFromNextJS',\n  error: String(error),\n  statusCode: String(statusCode),\n  response: JSON.stringify(response),\n  timestamp: new Date().toISOString(),\n  possibleCauses: [\n    'Next.js API not reachable',\n    'GOOGLE_REFRESH_TOKEN not set in Next.js .env.local',\n    'No active session in Next.js',\n    'N8N_API_KEY mismatch (if required)',\n    'Next.js server error'\n  ]\n};\n\nif (DEBUG) {\n  console.error('[LogTokenError] Token fetch failed:', errorDetails);\n}\n\nthrow new Error(`Token fetch failed: ${error}. Status: ${statusCode}. Check Next.js /api/gmail/token endpoint and GOOGLE_REFRESH_TOKEN in .env.local`);"
      },
      "id": "75954a66-fbe1-4697-8e69-ff9f4486271d",
      "name": "LogTokenError",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1872,
        1520
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const DEBUG = $env.DEBUG_N8N === 'true';\nconst tokenItem = $input.item;\nconst accessToken = tokenItem?.json?.accessToken || '';\nconst checkTokenItem = $('CheckToken').item;\n\nif (DEBUG) {\n  console.log('[MergeToken] Token response:', {\n    hasToken: !!accessToken,\n    tokenLength: accessToken.length,\n    tokenPreview: accessToken ? accessToken.substring(0, 20) + '...' : 'empty',\n    responseKeys: Object.keys(tokenItem.json || {})\n  });\n}\n\nif (!accessToken || accessToken.trim() === '') {\n  const errorMsg = 'No accessToken in Next.js API response. Response: ' + JSON.stringify(tokenItem.json || {});\n  if (DEBUG) {\n    console.error('[MergeToken]', errorMsg);\n  }\n  throw new Error(errorMsg + '. Please login via web or set GOOGLE_REFRESH_TOKEN in Next.js .env.local');\n}\n\nreturn {\n  json: {\n    accessToken: accessToken.trim(),\n    maxResults: checkTokenItem?.json?.maxResults || 50,\n    query: checkTokenItem?.json?.query || '',\n    resetStaticData: checkTokenItem?.json?.resetStaticData || false,\n    tokenSource: 'nextjs-api'\n  }\n};"
      },
      "id": "07734377-e843-45a3-8c53-5e4b3ad4ee5b",
      "name": "MergeToken",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1872,
        1392
      ]
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.needsToken || false }}",
              "value2": true
            }
          ]
        }
      },
      "id": "b0b5cfb0-ad29-4603-8417-5a5c31393518",
      "name": "CheckNeedsToken",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        1600,
        1216
      ]
    },
    {
      "parameters": {},
      "id": "0d2f890f-3abc-411e-8621-2f43efd1e562",
      "name": "RefreshToken",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 1,
      "position": [
        2032,
        1216
      ]
    },
    {
      "parameters": {
        "jsCode": "const DEBUG = $env.DEBUG_N8N === 'true';\nconst allItems = $input.all();\nconst currentItem = allItems[0] || {};\nconst messages = currentItem.json?.messages || [];\nconst resultSizeEstimate = currentItem.json?.resultSizeEstimate || 0;\nconst accessToken = $('Prepare1').item.json.accessToken || '';\n\nconst logData = {\n  node: 'ListMessages',\n  messageCount: messages.length,\n  resultSizeEstimate: resultSizeEstimate,\n  hasAccessToken: !!accessToken,\n  tokenLength: accessToken.length,\n  messageIds: messages.slice(0, 10).map(m => m.id || m.messageId),\n  timestamp: new Date().toISOString()\n};\n\nif (DEBUG) {\n  console.log('[LogListMessages] Gmail API response:', logData);\n}\n\nreturn [{ json: { \n  ...currentItem.json,\n  _debug: logData\n} }];"
      },
      "id": "c50d7173-b2e1-43ad-9e81-1ba138c9730e",
      "name": "LogListMessages",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2672,
        1024
      ]
    },
    {
      "parameters": {
        "jsCode": "const DEBUG = $env.DEBUG_N8N === 'true';\nconst allItems = $input.all();\nconst staticData = $getWorkflowStaticData('global');\nconst processedIds = staticData.processedMessageIds || [];\nconst currentItem = allItems[0] || {};\nconst currentMessages = currentItem.json?.messages || [];\nconst accessToken = $('Prepare1').item.json.accessToken || '';\nconst resetStaticData = $('Prepare1').item.json.resetStaticData || false;\n\nif (resetStaticData) {\n  staticData.processedMessageIds = [];\n  staticData.lastResetTime = new Date().toISOString();\n  if (DEBUG) {\n    console.log('[FilterNewMessages] StaticData reset requested');\n  }\n}\n\nconst allMessageIds = currentMessages.map(msg => String(msg.id || msg.messageId || '')).filter(id => id);\nconst newMessageIds = allMessageIds.filter(msgId => !processedIds.includes(msgId));\nconst excludedIds = allMessageIds.filter(msgId => processedIds.includes(msgId));\n\nconst newMessages = currentMessages.filter(msg => {\n  const msgId = String(msg.id || msg.messageId || '');\n  return msgId && !processedIds.includes(msgId);\n});\n\nconst debugInfo = {\n  node: 'FilterNewMessages',\n  totalMessagesFromGmail: currentMessages.length,\n  allMessageIds: allMessageIds,\n  processedIdsCount: processedIds.length,\n  processedIds: processedIds.slice(0, 20),\n  newMessageIds: newMessageIds,\n  excludedIds: excludedIds,\n  newMessagesCount: newMessages.length,\n  resetRequested: resetStaticData,\n  timestamp: new Date().toISOString()\n};\n\nif (DEBUG) {\n  console.log('[FilterNewMessages] Filtering results:', debugInfo);\n  if (newMessages.length === 0 && currentMessages.length > 0) {\n    console.warn('[FilterNewMessages] WARNING: All messages were already processed!', {\n      totalMessages: currentMessages.length,\n      processedCount: processedIds.length,\n      excludedIds: excludedIds.slice(0, 5)\n    });\n  }\n}\n\nif (newMessages.length === 0) {\n  return [{ \n    json: { \n      messages: [], \n      accessToken,\n      _debug: debugInfo,\n      _warning: currentMessages.length > 0 ? 'All messages already processed. Use resetStaticData=true to reset.' : 'No messages from Gmail API'\n    } \n  }];\n}\n\n// \uac00\uc7a5 \ucd5c\uadfc \uba54\uc2dc\uc9c0 1\uac1c\ub9cc \uc120\ud0dd (\ub0a0\uc9dc \uae30\uc900 \ub0b4\ub9bc\ucc28\uc21c \uc815\ub82c \ud6c4 \uccab \ubc88\uc9f8)\n// Gmail API\ub294 \ubcf4\ud1b5 \ucd5c\uc2e0 \uba54\uc2dc\uc9c0\uac00 \uba3c\uc800 \uc624\uc9c0\ub9cc, \ud655\uc2e4\ud558\uac8c \ud558\uae30 \uc704\ud574 \uc815\ub82c\nconst sortedMessages = newMessages.sort((a, b) => {\n  const dateA = a.internalDate || a.date || 0;\n  const dateB = b.internalDate || b.date || 0;\n  return parseInt(dateB) - parseInt(dateA); // \ub0b4\ub9bc\ucc28\uc21c (\ucd5c\uc2e0\uc774 \uba3c\uc800)\n});\n\nconst latestMessage = sortedMessages[0] || null;\nconst finalMessages = latestMessage ? [latestMessage] : [];\n\n// \uc120\ud0dd\ub41c \uba54\uc2dc\uc9c0 ID\ub97c \uc989\uc2dc staticData\uc5d0 \uc800\uc7a5\ud558\uc5ec \uc911\ubcf5 \uc2e4\ud589 \ubc29\uc9c0\nif (latestMessage) {\n  const msgId = String(latestMessage.id || latestMessage.messageId || '');\n  if (msgId && !processedIds.includes(msgId)) {\n    staticData.processedMessageIds = [...processedIds, msgId];\n    staticData.lastProcessedTime = new Date().toISOString();\n    if (DEBUG) {\n      console.log('[FilterNewMessages] Message ID saved to staticData immediately:', msgId);\n    }\n  }\n}\n\nif (DEBUG) {\n  console.log('[FilterNewMessages] Selected latest message:', {\n    totalNewMessages: newMessages.length,\n    selectedMessageId: latestMessage?.id || latestMessage?.messageId || 'none',\n    selectedDate: latestMessage?.internalDate || latestMessage?.date || 'none',\n    savedToStaticData: latestMessage ? true : false\n  });\n}\n\nreturn [{ \n  json: { \n    messages: finalMessages, \n    accessToken,\n    _debug: {\n      ...debugInfo,\n      selectedMessageCount: finalMessages.length,\n      totalNewMessages: newMessages.length\n    }\n  } \n}];"
      },
      "id": "ae3ef83d-8e45-4603-89bd-871555dfaa3c",
      "name": "FilterNewMessages",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2688,
        1216
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const item = $input.item;\nconst from = item.json.from || '';\nconst subject = item.json.subject || '';\nconst date = item.json.date || '';\nconst body = item.json.body || '';\nconst accessToken = item.json?.accessToken || '';\n\nreturn {\n  json: {\n    prompt: '\uc774 \uc774\uba54\uc77c\uc744 \ubd84\uc11d\ud558\uace0 priority\ub97c \uacb0\uc815\ud574\uc918. priority\ub294 low, medium, high \uc911 \ud558\ub098\uc5ec\uc57c \ud574.',\n    email: {\n      sender: from,\n      subject: subject,\n      date: date,\n      content: body\n    },\n    accessToken: accessToken\n  }\n};"
      },
      "id": "eeb90c40-1c55-4773-83b8-5e273e25192f",
      "name": "PrepareAnalyzeBody",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3792,
        1216
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const item = $input.item;\nconst extractItem = $('Extract1').item.json;\n\n// AI Agent\uc758 output\uc5d0\uc11c JSON \ucd94\ucd9c\nlet aiOutput = item.json?.output || item.json?.response || item.json?.text || '';\n\n// JSON \ubb38\uc790\uc5f4\uc5d0\uc11c JSON \uac1d\uccb4 \ucd94\ucd9c\nlet analysis = {};\ntry {\n  // JSON \ucf54\ub4dc \ube14\ub85d \uc81c\uac70\n  if (typeof aiOutput === 'string') {\n    // ```json ... ``` \ud615\uc2dd \uc81c\uac70\n    aiOutput = aiOutput.replace(/```json\\s*/g, '').replace(/```\\s*/g, '').trim();\n    \n    // JSON \uac1d\uccb4 \ucd94\ucd9c\n    const jsonMatch = aiOutput.match(/\\{[\\s\\S]*\\}/);\n    if (jsonMatch) {\n      analysis = JSON.parse(jsonMatch[0]);\n    } else {\n      analysis = JSON.parse(aiOutput);\n    }\n  } else if (typeof aiOutput === 'object') {\n    analysis = aiOutput;\n  }\n} catch (e) {\n  console.error('[ParseAnalysis] JSON \ud30c\uc2f1 \uc2e4\ud328:', e.message);\n  // \uae30\ubcf8\uac12 \uc124\uc815\n  analysis = {\n    category: 'other',\n    priority: 'low',\n    summary: String(aiOutput).substring(0, 200),\n    is_schedule: false,\n    reply_suggestion: '',\n    should_reply: false,\n    should_add_calendar: false\n  };\n}\n\n// accessToken\uc744 \uc5ec\ub7ec \uc18c\uc2a4\uc5d0\uc11c \ucc3e\uae30\nlet accessToken = extractItem.accessToken || '';\nif (!accessToken) {\n  // Prepare1 \ub178\ub4dc\uc5d0\uc11c \uac00\uc838\uc624\uae30\n  const prepareItem = $('Prepare1').item?.json;\n  accessToken = prepareItem?.accessToken || '';\n}\nif (!accessToken) {\n  // \ud658\uacbd \ubcc0\uc218\uc5d0\uc11c \uac00\uc838\uc624\uae30\n  accessToken = $env.GOOGLE_ACCESS_TOKEN || '';\n}\n\nreturn {\n  json: {\n    priority: analysis.priority || 'low',\n    summary: analysis.summary || '',\n    category: analysis.category || 'other',\n    is_schedule: analysis.is_schedule || false,\n    schedule_title: analysis.schedule_title || '',\n    schedule_start: analysis.schedule_start || '',\n    schedule_end: analysis.schedule_end || '',\n    schedule_description: analysis.schedule_description || '',\n    reply_suggestion: analysis.reply_suggestion || '',\n    should_reply: analysis.should_reply || false,\n    should_add_calendar: analysis.should_add_calendar || false,\n    emailId: extractItem.id || '',\n    emailSubject: extractItem.subject || '',\n    emailFrom: extractItem.from || '',\n    emailDate: extractItem.date || '',\n    accessToken: accessToken\n  }\n};"
      },
      "id": "10769163-c587-4986-ba9f-2da4ec3a7cbe",
      "name": "ParseAnalysis",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        5136,
        1232
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "priority-check",
              "leftValue": "={{ $json.priority || 'low' }}",
              "rightValue": "high",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "33c7760c-5719-4e5f-b787-18723368e13e",
      "name": "CheckPriority",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        5280,
        1232
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const item = $input.item;\nconst emailSubject = item.json.emailSubject || '';\nconst emailFrom = item.json.emailFrom || '';\nconst summary = item.json.summary || '';\nconst emailId = item.json.emailId || '';\nconst scheduleTitle = item.json.schedule_title || emailSubject;\nconst scheduleStart = item.json.schedule_start || '';\nconst scheduleEnd = item.json.schedule_end || '';\nconst scheduleDescription = item.json.schedule_description || summary;\n\n// accessToken\uc744 \uc5ec\ub7ec \uc18c\uc2a4\uc5d0\uc11c \ucc3e\uae30\nlet accessToken = item.json?.accessToken || '';\nif (!accessToken) {\n  // Prepare1 \ub178\ub4dc\uc5d0\uc11c \uac00\uc838\uc624\uae30\n  try {\n    const prepareItem = $('Prepare1').item?.json;\n    accessToken = prepareItem?.accessToken || '';\n  } catch (e) {\n    // Prepare1\uc5d0\uc11c \uac00\uc838\uc62c \uc218 \uc5c6\uc73c\uba74 \ub2e4\uc74c \uc18c\uc2a4 \uc2dc\ub3c4\n  }\n}\nif (!accessToken) {\n  // \ud658\uacbd \ubcc0\uc218\uc5d0\uc11c \uac00\uc838\uc624\uae30\n  accessToken = $env.GOOGLE_ACCESS_TOKEN || '';\n}\n\nlet startTime;\nlet endTime;\nlet reminderMinutes = 360; // \uae30\ubcf8\uac12: 6\uc2dc\uac04 (360\ubd84)\n\n// AI\uac00 \ucd94\ucd9c\ud55c schedule_start\uc640 schedule_end \uc0ac\uc6a9\nif (scheduleStart && scheduleEnd) {\n  try {\n    // ISO 8601 \ud615\uc2dd\uc778\uc9c0 \ud655\uc778\n    const startDate = new Date(scheduleStart);\n    const endDate = new Date(scheduleEnd);\n    if (!isNaN(startDate.getTime()) && !isNaN(endDate.getTime())) {\n      startTime = startDate.toISOString();\n      endTime = endDate.toISOString();\n      \n      // \ub9c8\uac10 \uc2dc\uac04(endTime) 6\uc2dc\uac04 \uc804\uc5d0 \uc54c\ub9bc\uc774 \uc624\ub3c4\ub85d \uacc4\uc0b0\n      // Google Calendar reminders\ub294 start time \uae30\uc900\uc774\ubbc0\ub85c,\n      // endTime - startTime - 6\uc2dc\uac04\uc744 minutes\ub85c \uacc4\uc0b0\n      const durationMs = endDate.getTime() - startDate.getTime();\n      const durationMinutes = Math.floor(durationMs / (1000 * 60));\n      // \uc77c\uc815 \uae38\uc774\uc5d0\uc11c 6\uc2dc\uac04(360\ubd84)\uc744 \ube7c\uc11c \ub9c8\uac10 6\uc2dc\uac04 \uc804\uc5d0 \uc54c\ub9bc\n      // \uc77c\uc815\uc774 6\uc2dc\uac04 \ubbf8\ub9cc\uc774\uba74 0\ubd84(\uc989\uc2dc \uc54c\ub9bc)\uc73c\ub85c \uc124\uc815\n      reminderMinutes = Math.max(0, durationMinutes - 360);\n    } else {\n      throw new Error('Invalid date format');\n    }\n  } catch (e) {\n    // \ud30c\uc2f1 \uc2e4\ud328 \uc2dc \uae30\ubcf8\uac12 \uc0ac\uc6a9\n    startTime = new Date(Date.now() + 3600000).toISOString();\n    endTime = new Date(Date.now() + 7200000).toISOString();\n    reminderMinutes = 360;\n  }\n} else {\n  // schedule \uc815\ubcf4\uac00 \uc5c6\uc73c\uba74 \uae30\ubcf8\uac12 \uc0ac\uc6a9\n  startTime = new Date(Date.now() + 3600000).toISOString();\n  endTime = new Date(Date.now() + 7200000).toISOString();\n  reminderMinutes = 360;\n}\n\nreturn {\n  json: {\n    title: scheduleTitle ? `[High Priority] ${scheduleTitle}` : `[High Priority] ${emailSubject}`,\n    description: `From: ${emailFrom}\\n\\n${scheduleDescription || summary}\\n\\nEmail ID: ${emailId}`,\n    startTime: startTime,\n    endTime: endTime,\n    timeZone: 'Asia/Seoul',\n    accessToken: accessToken,\n    reminderMinutes: reminderMinutes\n  }\n};"
      },
      "id": "ddb896a4-95a0-4d9f-b73e-f2bde0de007d",
      "name": "PrepareCalendar",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        5504,
        1120
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://www.googleapis.com/calendar/v3/calendars/primary/events",
        "authentication": "none",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $json.accessToken }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "bodyParameters": {
          "parameters": []
        },
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({\n  summary: $json.title || '\uc0c8 \uc77c\uc815',\n  description: $json.description || '',\n  start: {\n    dateTime: $json.startTime || '',\n    timeZone: $json.timeZone || 'Asia/Seoul'\n  },\n  end: {\n    dateTime: $json.endTime || $json.startTime || '',\n    timeZone: $json.timeZone || 'Asia/Seoul'\n  },\n  reminders: {\n    useDefault: false,\n    overrides: [\n      {\n        method: 'popup',\n        minutes: $json.reminderMinutes || 360\n      },\n      {\n        method: 'email',\n        minutes: $json.reminderMinutes || 360\n      }\n    ]\n  }\n}) }}",
        "options": {
          "timeout": 60000,
          "retry": {
            "maxRetries": 2,
            "retryOnFail": true
          }
        }
      },
      "id": "d9200971-2f8a-48b6-a3be-4652596ca2ab",
      "name": "AddToCalendar",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        5728,
        1120
      ],
      "continueOnFail": true
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const DEBUG = $env.DEBUG_N8N === 'true';\nconst item = $input.item;\nconst accessToken = item.json?.accessToken || '';\nconst resetStaticData = item.json?.resetStaticData || false;\n\nif (DEBUG) {\n  console.log('[Prepare] Input:', {\n    hasToken: !!accessToken,\n    tokenSource: item.json?.tokenSource || 'unknown',\n    resetStaticData\n  });\n}\n\nif (!accessToken || accessToken.trim() === '') {\n  const envToken = $env.GOOGLE_ACCESS_TOKEN || '';\n  if (!envToken || envToken.trim() === '') {\n    const errorMsg = 'Missing accessToken: No token provided from webhook, refresh token, or environment variable';\n    if (DEBUG) {\n      console.error('[Prepare]', errorMsg);\n    }\n    throw new Error(errorMsg);\n  }\n  if (DEBUG) {\n    console.log('[Prepare] Using GOOGLE_ACCESS_TOKEN from env');\n  }\n  return {\n    json: {\n      accessToken: String(envToken).trim(),\n      maxResults: item.json?.maxResults || 50,\n      query: item.json?.query || '',\n      resetStaticData: resetStaticData,\n      tokenSource: 'environment'\n    }\n  };\n}\n\nreturn {\n  json: {\n    accessToken: String(accessToken).trim(),\n    maxResults: item.json?.maxResults || 50,\n    query: item.json?.query || '',\n    resetStaticData: resetStaticData,\n    tokenSource: item.json?.tokenSource || 'unknown'\n  }\n};"
      },
      "id": "992e19c0-40d2-4a36-8225-c138f4e46466",
      "name": "Prepare1",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2256,
        1216
      ]
    },
    {
      "parameters": {
        "url": "https://gmail.googleapis.com/gmail/v1/users/me/messages",
        "options": {},
        "headerParametersUi": {
          "parameter": [
            {
              "name": "Authorization",
              "value": "={{ 'Bearer ' + $json.accessToken }}"
            }
          ]
        },
        "queryParametersUi": {
          "parameter": [
            {
              "name": "maxResults",
              "value": "={{ $json.maxResults }}"
            },
            {
              "name": "q",
              "value": "={{ $json.query }}"
            }
          ]
        }
      },
      "id": "e2f5278f-3b6f-4e3a-bf87-6497f5f963b1",
      "name": "ListMessages1",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        2480,
        1216
      ]
    },
    {
      "parameters": {
        "jsCode": "const DEBUG = $env.DEBUG_N8N === 'true';\nconst allItems = $input.all();\nconst currentItem = allItems[0] || {};\nconst messages = currentItem.json?.messages || [];\nconst accessToken = currentItem.json?.accessToken || $('Prepare1').item.json.accessToken || '';\n\nif (DEBUG) {\n  console.log('[WrapMessages] Wrapping messages:', {\n    messageCount: messages.length,\n    hasAccessToken: !!accessToken\n  });\n}\n\n// \uac00\uc7a5 \ucd5c\uadfc \uba54\uc2dc\uc9c0 1\uac1c\ub9cc \uc120\ud0dd (\uc774\ubbf8 FilterNewMessages\uc5d0\uc11c 1\uac1c\ub9cc \uc120\ud0dd\ud588\uc9c0\ub9cc, \ud655\uc2e4\ud558\uac8c \ud558\uae30 \uc704\ud574)\nconst latestMessage = messages[0] || null;\nconst items = latestMessage ? [{\n  id: String(latestMessage.id || latestMessage.messageId || ''),\n  threadId: String(latestMessage.threadId || ''),\n  accessToken: String(accessToken)\n}] : [];\n\nif (DEBUG) {\n  console.log('[WrapMessages] Selected message:', {\n    totalMessages: messages.length,\n    selectedMessageId: latestMessage?.id || latestMessage?.messageId || 'none',\n    itemsCount: items.length\n  });\n}\n\nreturn [{ json: { items } }];"
      },
      "id": "e6b2bfcd-7c02-44ca-ad30-48b50d614144",
      "name": "WrapMessages1",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2912,
        1216
      ]
    },
    {
      "parameters": {
        "fieldToSplitOut": "items",
        "options": {}
      },
      "id": "3f5b90fd-1881-4c59-84f1-aa5895f4ad88",
      "name": "Split1",
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [
        3136,
        1216
      ]
    },
    {
      "parameters": {
        "url": "={{ 'https://gmail.googleapis.com/gmail/v1/users/me/messages/' + String($json.id || '') + '?format=full' }}",
        "options": {},
        "headerParametersUi": {
          "parameter": [
            {
              "name": "Authorization",
              "value": "={{ 'Bearer ' + $json.accessToken }}"
            }
          ]
        }
      },
      "id": "b27fdf89-610e-4453-9aac-78d8996d935a",
      "name": "GetMail1",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        3360,
        1216
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const DEBUG = $env.DEBUG_N8N === 'true';\nconst item = $input.item;\nconst payload = item.json.payload || {};\nconst headers = Array.isArray(payload.headers) ? payload.headers : [];\n\n// accessToken\uc744 \uc5ec\ub7ec \uc18c\uc2a4\uc5d0\uc11c \ucc3e\uae30\nlet accessToken = '';\n// 1. GetMail1 \ub178\ub4dc\uc758 \uc785\ub825\uc5d0\uc11c \uac00\uc838\uc624\uae30 (Split1\uc5d0\uc11c \ubd84\ub9ac\ub41c \uc544\uc774\ud15c)\ntry {\n  const getMailInput = $('GetMail1').item?.json;\n  accessToken = getMailInput?.accessToken || '';\n} catch (e) {\n  // GetMail1\uc5d0\uc11c \uac00\uc838\uc62c \uc218 \uc5c6\uc73c\uba74 \ub2e4\uc74c \uc18c\uc2a4 \uc2dc\ub3c4\n}\n\n// 2. Prepare1 \ub178\ub4dc\uc5d0\uc11c \uac00\uc838\uc624\uae30\nif (!accessToken) {\n  try {\n    const prepareItem = $('Prepare1').item?.json;\n    accessToken = prepareItem?.accessToken || '';\n  } catch (e) {\n    // Prepare1\uc5d0\uc11c\ub3c4 \uac00\uc838\uc62c \uc218 \uc5c6\uc73c\uba74 \ub2e4\uc74c \uc18c\uc2a4 \uc2dc\ub3c4\n  }\n}\n\n// 3. \ud658\uacbd \ubcc0\uc218\uc5d0\uc11c \uac00\uc838\uc624\uae30\nif (!accessToken) {\n  accessToken = $env.GOOGLE_ACCESS_TOKEN || '';\n}\n\nconst messageId = String(item.json.id || '');\n\nif (DEBUG) {\n  console.log('[Extract] Processing message:', {\n    messageId: messageId,\n    hasPayload: !!payload,\n    hasHeaders: headers.length > 0,\n    hasAccessToken: !!accessToken,\n    tokenLength: accessToken.length\n  });\n}\n\nconst subjectHeader = headers.find(h => h && (h.name === 'Subject' || h.name === 'subject'));\nconst fromHeader = headers.find(h => h && (h.name === 'From' || h.name === 'from'));\nconst dateHeader = headers.find(h => h && (h.name === 'Date' || h.name === 'date'));\n\nlet body = '';\nif (payload.body?.data) {\n  const b64 = payload.body.data.replace(/-/g, '+').replace(/_/g, '/');\n  try {\n    body = Buffer.from(b64, 'base64').toString('utf-8');\n  } catch(e) {\n    if (DEBUG) console.warn('[Extract] Body decode error:', e.message);\n    body = '';\n  }\n}\nif (!body && Array.isArray(payload.parts)) {\n  for (const p of payload.parts) {\n    if (p.body?.data) {\n      const b64 = p.body.data.replace(/-/g, '+').replace(/_/g, '/');\n      try {\n        body = Buffer.from(b64, 'base64').toString('utf-8');\n        break;\n      } catch(e) {\n        if (DEBUG) console.warn('[Extract] Part decode error:', e.message);\n        continue;\n      }\n    }\n  }\n}\n\nconst extracted = {\n  id: messageId,\n  subject: subjectHeader?.value || '(\uc81c\ubaa9 \uc5c6\uc74c)',\n  from: fromHeader?.value || '',\n  date: dateHeader?.value || '',\n  body: body,\n  accessToken: accessToken\n};\n\nif (DEBUG) {\n  console.log('[Extract] Extracted data:', {\n    id: extracted.id,\n    subject: extracted.subject.substring(0, 50),\n    from: extracted.from,\n    hasBody: !!extracted.body,\n    hasAccessToken: !!extracted.accessToken\n  });\n}\n\nreturn { json: extracted };"
      },
      "id": "0276a71b-bd4a-495d-bb11-ceb9a3a1be28",
      "name": "Extract1",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3568,
        1216
      ]
    },
    {
      "parameters": {},
      "id": "9cf54a61-688d-4301-8ff7-aadf9f3f6f78",
      "name": "Merge1",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 1,
      "position": [
        5504,
        1328
      ]
    },
    {
      "parameters": {
        "jsCode": "const DEBUG = $env.DEBUG_N8N === 'true';\nconst allItems = $input.all();\nconst staticData = $getWorkflowStaticData('global');\nconst processedIds = staticData.processedMessageIds || [];\nconst newIds = allItems.map(item => String(item.json.id || item.json.emailId || '')).filter(id => id && !processedIds.includes(id));\n\nif (DEBUG) {\n  console.log('[UpdateStaticData] Updating:', {\n    currentProcessedCount: processedIds.length,\n    newIdsToAdd: newIds.length,\n    newIds: newIds.slice(0, 10)\n  });\n}\n\nif (newIds.length > 0) {\n  staticData.processedMessageIds = [...processedIds, ...newIds];\n  staticData.lastUpdateTime = new Date().toISOString();\n}\n\nreturn allItems;"
      },
      "id": "2f3bed43-78a8-4569-b93e-26dad6116696",
      "name": "UpdateStaticData1",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        5936,
        1344
      ]
    },
    {
      "parameters": {
        "jsCode": "const allItems = $input.all();\nconst messages = allItems.map(item => ({\n  id: item.json.id || item.json.emailId || '',\n  subject: item.json.subject || item.json.emailSubject || '(\uc81c\ubaa9 \uc5c6\uc74c)',\n  from: item.json.from || item.json.emailFrom || '',\n  date: item.json.date || item.json.emailDate || '',\n  priority: item.json.priority || 'low',\n  summary: item.json.summary || ''\n}));\n\nreturn [{ json: { messages } }];"
      },
      "id": "89d53a15-650f-4af4-a163-ba6c00dab1fb",
      "name": "FormatResponse2",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        6160,
        1232
      ]
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=\uc774 \uc774\uba54\uc77c \uc815\ubcf4\ub97c \ubd84\uc11d\ud558\uace0 \ub2e4\uc74c\uc744 **JSON \ud615\uc2dd\uc73c\ub85c\ub9cc \uc815\ud655\ud788 \ubc18\ud658\ud574\uc8fc\uc138\uc694. JSON \uc2a4\ud0a4\ub9c8\ub97c \uc5c4\uaca9\ud558\uac8c \uc900\uc218\ud574\uc57c \ud569\ub2c8\ub2e4. \ub2e4\ub978 \uc124\uba85\uc774\ub098 \ud14d\uc2a4\ud2b8\ub294 \uc77c\uc808 \ud3ec\ud568\ud558\uc9c0 \ub9c8\uc138\uc694.\n\n**[\uc694\uccad JSON \uc2a4\ud0a4\ub9c8]**\n{\n  \"category\": \"work\" | \"payment\" | \"ad\" | \"other\" | \"\uc77c\uc815\",\n  \"priority\": \"low\" | \"middle\" | \"high\",\n  \"summary\": \"\uc774\uba54\uc77c \uc694\uc57d (\ud55c\uad6d\uc5b4, 3\ubb38\uc7a5 \uc774\ud558)\",\n  \"is_schedule\": true/false,\n  \"schedule_title\": \"\uc77c\uc815 \uc81c\ubaa9 (\uc77c\uc815 \uad00\ub828 \ub0b4\uc6a9\uc774 \uc788\uc744 \uacbd\uc6b0)\",\n  \"schedule_start\": \"ISO 8601 \ud615\uc2dd \uc2dc\uc791 \uc2dc\uac04 (\uc77c\uc815\uc778 \uacbd\uc6b0, \uc2dc\uac04\uc815\ubcf4 \ud3ec\ud568)\",\n  \"schedule_end\": \"ISO 8601 \ud615\uc2dd \uc885\ub8cc \uc2dc\uac04 (\uc77c\uc815\uc778 \uacbd\uc6b0, \uc2dc\uac04\uc815\ubcf4 \ud3ec\ud568)\",\n  \"schedule_description\": \"\uc77c\uc815 \uc124\uba85 (\uc77c\uc815\uc778 \uacbd\uc6b0)\",\n  \"reply_suggestion\": \"\ub2f5\uc7a5 \ucd94\ucc9c \ub0b4\uc6a9 (\ud55c\uad6d\uc5b4, \uc989\uc2dc \ubcf4\ub0bc \uc218 \uc788\ub294 \uc644\uc131\ub41c \ubb38\uc7a5)\",\n  \"should_reply\": true/false,\n  \"should_add_calendar\": true/false\n}\n\n**[\uc774\uba54\uc77c \uc815\ubcf4]**\nFrom: {{ $json.email.from || $json.from }}\nSubject: {{ $json.email.subject || $json.subject }}\nDate: {{ $json.email.date || $json.date }}\nBody:\n{{ $json.email.body || $json.body || $json.email.content || $json.content }}\n\n**[\uc911\uc694 \uc9c0\uce68]**\n1. **priority**\ub294 \uc774\uba54\uc77c\uc758 \uc911\uc694\ub3c4\uc640 \uae34\uae09\uc131\uc744 \uace0\ub824\ud558\uc5ec low, middle, high \uc911 \ud558\ub098\ub85c \ubd84\ub958\ud558\uc138\uc694.\n2. **is_schedule**\uc774 true\uc774\uace0 **should_add_calendar**\uac00 true\uc774\uba74 \ub2e4\uc74c \uce98\ub9b0\ub354 \ub178\ub4dc\uc5d0\uc11c \uc790\ub3d9\uc73c\ub85c \uc77c\uc815\uc744 \ucd94\uac00\ud560 \uc218 \uc788\ub3c4\ub85d \uc2dc\uac04 \uc815\ubcf4(schedule_start, schedule_end)\ub97c ISO 8601 \ud615\uc2dd\uc73c\ub85c \uc815\ud655\ud788 \ucd94\ucd9c\ud558\uc138\uc694.\n3. **reply_suggestion**\uc740 \uc2e4\uc81c\ub85c \ubcf4\ub0bc \uc218 \uc788\ub294 \uc804\ubb38\uc801\uc778 \ub2f5\uc7a5 \ub0b4\uc6a9\uc744 \uc791\uc131\ud558\uc138\uc694.\n4. **JSON \ud615\uc2dd\ub9cc \ubc18\ud658**\ud558\uace0 \ud504\ub86c\ud504\ud2b8\uc5d0 \ub300\ud55c \uc5b4\ub5a0\ud55c \uc124\uba85\uc774\ub098 \ucd94\uac00\uc801\uc778 \uc778\uc0ac\ub9d0\ub3c4 \ud3ec\ud568\ud558\uc9c0 \ub9c8\uc138\uc694.",
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 3,
      "position": [
        4176,
        1216
      ],
      "id": "c0e532f5-42ae-4a17-9f79-21c84830410e",
      "name": "AI Agent"
    },
    {
      "parameters": {
        "model": "solar-pro2",
        "options": {}
      },
      "type": "n8n-nodes-upstage.lmChatModelUpstage",
      "typeVersion": 1,
      "position": [
        4176,
        1616
      ],
      "id": "d760e0ca-0099-458b-b009-d2f7f3187f95",
      "name": "Upstage Solar Chat for Agent",
      "credentials": {
        "upstageApi": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "MergeTriggers",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "MergeTriggers": {
      "main": [
        [
          {
            "node": "CheckToken",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "CheckToken": {
      "main": [
        [
          {
            "node": "CheckNeedsToken",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GetTokenFromNextJS": {
      "main": [
        [
          {
            "node": "MergeToken",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "LogTokenError",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "CheckNeedsToken": {
      "main": [
        [
          {
            "node": "GetTokenFromNextJS",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "RefreshToken",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "MergeToken": {
      "main": [
        [
          {
            "node": "RefreshToken",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "RefreshToken": {
      "main": [
        [
          {
            "node": "Prepare1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "FilterNewMessages": {
      "main": [
        [
          {
            "node": "WrapMessages1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PrepareAnalyzeBody": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ParseAnalysis": {
      "main": [
        [
          {
            "node": "CheckPriority",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "CheckPriority": {
      "main": [
        [
          {
            "node": "PrepareCalendar",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Merge1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PrepareCalendar": {
      "main": [
        [
          {
            "node": "AddToCalendar",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare1": {
      "main": [
        [
          {
            "node": "ListMessages1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ListMessages1": {
      "main": [
        [
          {
            "node": "LogListMessages",
            "type": "main",
            "index": 0
          },
          {
            "node": "FilterNewMessages",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "WrapMessages1": {
      "main": [
        [
          {
            "node": "Split1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split1": {
      "main": [
        [
          {
            "node": "GetMail1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GetMail1": {
      "main": [
        [
          {
            "node": "Extract1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract1": {
      "main": [
        [
          {
            "node": "PrepareAnalyzeBody",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AddToCalendar": {
      "main": [
        [
          {
            "node": "Merge1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge1": {
      "main": [
        [
          {
            "node": "UpdateStaticData1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "UpdateStaticData1": {
      "main": [
        [
          {
            "node": "FormatResponse2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent": {
      "main": [
        [
          {
            "node": "ParseAnalysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upstage Solar Chat for Agent": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1",
    "callerPolicy": "workflowsFromSameOwner",
    "availableInMCP": false,
    "errorWorkflow": "EnfNTIj9JVzQBTrl"
  },
  "versionId": "41236f96-6c32-43d2-b25f-af33cff0f4e9",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "EnfNTIj9JVzQBTrl",
  "tags": []
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Gmail AI Agent - 자동 분류 및 처리. Uses httpRequest, agent, n8n-nodes-upstage. Webhook trigger; 26 nodes.

Source: https://github.com/oktochoi/DOBIDOBI/blob/6d4ba810e70f607c5e6ce6a404dc31f951b45785/n8n/M&Z.json — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

⏺ 🚀 How it works

Agent, Anthropic Chat, Output Parser Structured +6
AI & RAG

L&D_AgentsAI_ATIVO. Uses httpRequest, agent, googleCalendarTool, toolSerpApi. Webhook trigger; 93 nodes.

HTTP Request, Agent, Google Calendar Tool +9
AI & RAG

This n8n workflow orchestrates a powerful suite of AI Agents and automations to manage and optimize various aspects of an e-commerce operation, particularly for platforms like Shopify. It leverages La

Google Sheets, HTTP Request, Slack +10
AI & RAG

Lead Pipeline v3.0. Uses httpRequest, agent, lmChatAnthropic, toolThink. Webhook trigger; 77 nodes.

HTTP Request, Agent, Anthropic Chat +4
AI & RAG

What if AI didn't just write content—but actually thought about how to write it? This n8n workflow revolutionizes content creation by deploying multiple specialized AI agents that handle every aspect

Tool Http Request, Anthropic Chat, Airtable +7