{
  "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": []
}