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": "\ud30c\uc774\ud504\ub77c\uc778 \u2462 \ub9e4\ub274\uc5bc \uc790\ub3d9 \uc5c5\ub370\uc774\ud2b8",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "hours",
"hour": 9
}
]
}
},
"id": "schedule-trigger",
"name": "\ub9e4\uc77c \uc624\uc804 9\uc2dc",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
0,
0
]
},
{
"parameters": {
"method": "POST",
"url": "https://api.notion.com/v1/databases/{{ $env.NOTION_VOC_DB_ID }}/query",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "notionApi",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Notion-Version",
"value": "2022-06-28"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"filter\": {\n \"and\": [\n {\n \"property\": \"\uc218\uc9d1\uc77c\",\n \"date\": {\n \"past_week\": {}\n }\n },\n {\n \"property\": \"\ucc98\ub9ac\uc0c1\ud0dc\",\n \"select\": {\n \"equals\": \"\uc2e0\uaddc\"\n }\n }\n ]\n }\n}"
},
"id": "get-recent-voc",
"name": "\ucd5c\uadfc 7\uc77c VOC \uc870\ud68c",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
220,
0
]
},
{
"parameters": {
"jsCode": "// \uc9c8\ubb38 \uc720\ud615\ubcc4 \ud074\ub7ec\uc2a4\ud130\ub9c1 (\ud0a4\uc6cc\ub4dc \uae30\ubc18)\nconst vocData = $input.first().json;\n\nif (!vocData.results || vocData.results.length === 0) {\n return [{ json: { has_patterns: false, message: '\ubd84\uc11d\ud560 VOC \uc5c6\uc74c' } }];\n}\n\n// \uc0c1\ud488\ubcc4, \uce74\ud14c\uace0\ub9ac\ubcc4 \uc9c8\ubb38 \uadf8\ub8f9\ud551\nconst patterns = {};\n\nfor (const item of vocData.results) {\n const productName = item.properties['\uc0c1\ud488\uba85']?.rich_text?.[0]?.text?.content || 'Unknown';\n const category = item.properties['\uce74\ud14c\uace0\ub9ac']?.select?.name || '\uae30\ud0c0';\n const question = item.properties['\uc9c8\ubb38']?.rich_text?.[0]?.text?.content || '';\n \n const key = `${productName}::${category}`;\n \n if (!patterns[key]) {\n patterns[key] = {\n product_name: productName,\n category: category,\n questions: [],\n count: 0\n };\n }\n \n patterns[key].questions.push(question);\n patterns[key].count++;\n}\n\n// 3\ud68c \uc774\uc0c1 \ubc18\ubcf5\ub41c \ud328\ud134\ub9cc \ucd94\ucd9c\nconst repeatedPatterns = Object.values(patterns).filter(p => p.count >= 3);\n\nif (repeatedPatterns.length === 0) {\n return [{ json: { has_patterns: false, message: '\ubc18\ubcf5 \ud328\ud134 \uc5c6\uc74c (\uc784\uacc4\uac12: 3\ud68c)' } }];\n}\n\nreturn [{\n json: {\n has_patterns: true,\n patterns: repeatedPatterns,\n analyzed_at: new Date().toISOString()\n }\n}];"
},
"id": "cluster-questions",
"name": "\uc9c8\ubb38 \uc720\ud615 \ud074\ub7ec\uc2a4\ud130\ub9c1",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
440,
0
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"leftValue": "={{ $json.has_patterns }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
],
"combinator": "and"
}
},
"id": "if-has-patterns",
"name": "\ubc18\ubcf5 \ud328\ud134 \uc788\uc74c?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
660,
0
]
},
{
"parameters": {
"method": "POST",
"url": "https://api.notion.com/v1/databases/{{ $env.NOTION_MANUAL_DB_ID }}/query",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "notionApi",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Notion-Version",
"value": "2022-06-28"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"filter\": {\n \"property\": \"\uc0c1\ud488\uba85\",\n \"title\": {\n \"contains\": \"{{ $json.patterns[0].product_name }}\"\n }\n }\n}"
},
"id": "get-existing-manual",
"name": "\uae30\uc874 \ub9e4\ub274\uc5bc \uc870\ud68c",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
880,
-100
]
},
{
"parameters": {
"jsCode": "// \uae30\uc874 \ub9e4\ub274\uc5bc\uacfc \uc2e0\uaddc \uc9c8\ubb38 \ub370\uc774\ud130 \ubcd1\ud569\nconst patternsData = $('\ubc18\ubcf5 \ud328\ud134 \uc788\uc74c?').first().json;\nconst manualData = $input.first().json;\n\nlet existingFaq = '';\nif (manualData.results && manualData.results.length > 0) {\n existingFaq = manualData.results[0].properties['FAQ']?.rich_text?.[0]?.text?.content || '';\n}\n\nreturn [{\n json: {\n product_name: patternsData.patterns[0].product_name,\n category: patternsData.patterns[0].category,\n existing_faq: existingFaq,\n new_questions: patternsData.patterns[0].questions,\n question_count: patternsData.patterns[0].count\n }\n}];"
},
"id": "merge-for-gemini",
"name": "Gemini \uc785\ub825 \uc900\ube44",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1100,
-100
]
},
{
"parameters": {
"method": "POST",
"url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent",
"authentication": "genericCredentialType",
"genericAuthType": "httpQueryAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"contents\": [{\n \"parts\": [{\n \"text\": \"\ub2f9\uc2e0\uc740 CS \ub9e4\ub274\uc5bc \uad00\ub9ac\uc790\uc785\ub2c8\ub2e4.\\n\\n[\uae30\uc874 \ub9e4\ub274\uc5bc FAQ]\\n{{ $json.existing_faq }}\\n\\n[\uc2e0\uaddc \uace0\uac1d \uc9c8\ubb38\ub4e4 - {{ $json.question_count }}\uac74 \ubc18\ubcf5]\\n{{ $json.new_questions.join('\\n') }}\\n\\n\uc704 \uc2e0\uaddc \uc9c8\ubb38\ub4e4\uc744 \ubd84\uc11d\ud558\uc5ec:\\n1. \uae30\uc874 \ub9e4\ub274\uc5bc\uc5d0\uc11c \ubd80\uc871\ud55c \ubd80\ubd84 \uc2dd\ubcc4\\n2. \ucd94\uac00\ud574\uc57c \ud560 FAQ \ud56d\ubaa9 \uc81c\uc548\\n3. \uc218\uc815\uc774 \ud544\uc694\ud55c \uae30\uc874 \ub2f5\ubcc0 \uac1c\uc120\uc548\\n\\n[\ucd9c\ub825 \ud615\uc2dd - JSON]\\n{\\n \\\"analysis\\\": \\\"\ubd80\uc871\ud55c \ubd80\ubd84 \ubd84\uc11d\\\",\\n \\\"new_faq\\\": [\\n {\\\"q\\\": \\\"\ucd94\uac00 \uc9c8\ubb381\\\", \\\"a\\\": \\\"\uad8c\uc7a5 \ub2f5\ubcc01\\\"}\\n ],\\n \\\"improvements\\\": \\\"\uae30\uc874 \ub2f5\ubcc0 \uac1c\uc120 \uc81c\uc548\\\"\\n}\"\n }]\n }],\n \"generationConfig\": {\n \"temperature\": 0.7,\n \"maxOutputTokens\": 2048\n }\n}"
},
"id": "gemini-analyze",
"name": "Gemini \ubcf4\uc644\uc548 \uc0dd\uc131",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1320,
-100
]
},
{
"parameters": {
"jsCode": "// Gemini \uc751\ub2f5 \ud30c\uc2f1\nconst response = $input.first().json;\nconst inputData = $('Gemini \uc785\ub825 \uc900\ube44').first().json;\n\nlet suggestion = {};\ntry {\n const text = response.candidates[0].content.parts[0].text;\n const jsonMatch = text.match(/\\{[\\s\\S]*\\}/);\n if (jsonMatch) {\n suggestion = JSON.parse(jsonMatch[0]);\n }\n} catch (e) {\n suggestion = {\n analysis: '\ud30c\uc2f1 \uc2e4\ud328',\n new_faq: [],\n improvements: ''\n };\n}\n\nreturn [{\n json: {\n product_name: inputData.product_name,\n category: inputData.category,\n ...suggestion,\n generated_at: new Date().toISOString(),\n status: '\uac80\ud1a0\ub300\uae30'\n }\n}];"
},
"id": "parse-suggestion",
"name": "\ubcf4\uc644\uc548 \ud30c\uc2f1",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1540,
-100
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $env.JANDI_WEBHOOK_URL }}",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"body\": \"[\ub9e4\ub274\uc5bc \uc5c5\ub370\uc774\ud2b8] \uac80\ud1a0 \uc694\uccad\",\n \"connectColor\": \"#FAC11B\",\n \"connectInfo\": [\n {\n \"title\": \"\uc0c1\ud488\",\n \"description\": \"{{ $json.product_name }}\"\n },\n {\n \"title\": \"\ubc18\ubcf5 \uc9c8\ubb38 \uc720\ud615\",\n \"description\": \"{{ $json.category }}\"\n },\n {\n \"title\": \"\ubd84\uc11d \uacb0\uacfc\",\n \"description\": \"{{ $json.analysis }}\"\n },\n {\n \"title\": \"\ucd94\uac00 FAQ \uc218\",\n \"description\": \"{{ $json.new_faq.length }}\uac74\"\n }\n ]\n}"
},
"id": "jandi-review-request",
"name": "\uc794\ub514 \uac80\ud1a0 \uc694\uccad",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1760,
-100
]
},
{
"parameters": {},
"id": "no-pattern",
"name": "\ud328\ud134 \uc5c6\uc74c - \uc885\ub8cc",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [
880,
100
]
}
],
"connections": {
"\ub9e4\uc77c \uc624\uc804 9\uc2dc": {
"main": [
[
{
"node": "\ucd5c\uadfc 7\uc77c VOC \uc870\ud68c",
"type": "main",
"index": 0
}
]
]
},
"\ucd5c\uadfc 7\uc77c VOC \uc870\ud68c": {
"main": [
[
{
"node": "\uc9c8\ubb38 \uc720\ud615 \ud074\ub7ec\uc2a4\ud130\ub9c1",
"type": "main",
"index": 0
}
]
]
},
"\uc9c8\ubb38 \uc720\ud615 \ud074\ub7ec\uc2a4\ud130\ub9c1": {
"main": [
[
{
"node": "\ubc18\ubcf5 \ud328\ud134 \uc788\uc74c?",
"type": "main",
"index": 0
}
]
]
},
"\ubc18\ubcf5 \ud328\ud134 \uc788\uc74c?": {
"main": [
[
{
"node": "\uae30\uc874 \ub9e4\ub274\uc5bc \uc870\ud68c",
"type": "main",
"index": 0
}
],
[
{
"node": "\ud328\ud134 \uc5c6\uc74c - \uc885\ub8cc",
"type": "main",
"index": 0
}
]
]
},
"\uae30\uc874 \ub9e4\ub274\uc5bc \uc870\ud68c": {
"main": [
[
{
"node": "Gemini \uc785\ub825 \uc900\ube44",
"type": "main",
"index": 0
}
]
]
},
"Gemini \uc785\ub825 \uc900\ube44": {
"main": [
[
{
"node": "Gemini \ubcf4\uc644\uc548 \uc0dd\uc131",
"type": "main",
"index": 0
}
]
]
},
"Gemini \ubcf4\uc644\uc548 \uc0dd\uc131": {
"main": [
[
{
"node": "\ubcf4\uc644\uc548 \ud30c\uc2f1",
"type": "main",
"index": 0
}
]
]
},
"\ubcf4\uc644\uc548 \ud30c\uc2f1": {
"main": [
[
{
"node": "\uc794\ub514 \uac80\ud1a0 \uc694\uccad",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
How this works
This workflow automates the daily update of your customer service manual by analysing recent customer feedback to identify gaps and generate targeted improvements, saving hours of manual review and ensuring your support team always has the latest guidance. It is ideal for customer support managers or product teams handling high volumes of voice-of-customer (VOC) data, particularly in multilingual environments like Korean operations. The key step involves querying the past seven days' VOC via HTTP requests, clustering question patterns with custom code, and using Google Gemini to create precise supplement recommendations based on existing manual content.
Use this workflow when you need proactive, scheduled enhancements to service documentation driven by real customer interactions, such as in e-commerce or tech support pipelines running every morning. Avoid it for one-off audits or if your VOC data lacks structure, as it relies on consistent API access. Common variations include adjusting the cron schedule for weekly runs or integrating additional APIs like Zendesk for broader feedback sources.
About this workflow
파이프라인 ③ 매뉴얼 자동 업데이트. Uses scheduleTrigger, httpRequest, noOp. Scheduled trigger; 10 nodes.
Source: https://github.com/USEONGEE/n8n-example/blob/8d817d796e277931d4ecd47aaad930bf3afcca9c/.n8n/pipeline-3-manual-update.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.
WF-Main - XHS 主控制器. Uses scheduleTrigger, httpRequest, executeWorkflow, noOp. Scheduled trigger; 21 nodes.
Dm-Profile-Visitors. Uses httpRequest, googleSheets. Scheduled trigger; 21 nodes.
RSS to Multi-Channel Social (X / LinkedIn / Discord). Uses stickyNote, scheduleTrigger, httpRequest. Scheduled trigger; 19 nodes.
YouTube Channel to Notion. Uses stickyNote, scheduleTrigger, httpRequest, noOp. Scheduled trigger; 18 nodes.
Automate Droplet Snapshots On Digitalocean. Uses httpRequest, stickyNote. Scheduled trigger; 17 nodes.