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 →
{
"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.
upstageApi
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 →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
⏺ 🚀 How it works
L&D_AgentsAI_ATIVO. Uses httpRequest, agent, googleCalendarTool, toolSerpApi. Webhook trigger; 93 nodes.
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
Lead Pipeline v3.0. Uses httpRequest, agent, lmChatAnthropic, toolThink. Webhook trigger; 77 nodes.
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