AutomationFlowsData & Sheets › Multi-channel Customer Sentiment Tracker with Real-time Analytics and Alerting

Multi-channel Customer Sentiment Tracker with Real-time Analytics and Alerting

ByCheng Siong Chin @cschin on n8n.io

Scheduled processes retrieve customer feedback from multiple channels. The system performs sentiment analysis to classify tone, then uses OpenAI models to extract themes, topics, and urgency indicators. All processed results are stored in a centralized database for trend…

Cron / scheduled trigger★★★★★ complexityAI-powered40 nodesHTTP RequestLm Chat Azure Open AiSentiment AnalysisOpenAIPostgresSlackEmail SendGoogle Sheets
Data & Sheets Trigger: Cron / scheduled Nodes: 40 Complexity: ★★★★★ AI nodes: yes Added:
Multi-channel Customer Sentiment Tracker with Real-time Analytics and Alerting — n8n workflow card showing HTTP Request, Lm Chat Azure Open Ai, Sentiment Analysis integration

This workflow corresponds to n8n.io template #10762 — we link there as the canonical source.

This workflow follows the Emailsend → Google Sheets 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
{
  "id": "GPcyP8vbbE3JZMz9",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Multi-Channel Customer Sentiment Tracker with Real-Time Analytics and Alerting",
  "tags": [],
  "nodes": [
    {
      "id": "08d85f69-8c27-4b0c-9ae2-a148192cb0ec",
      "name": "Schedule Trigger - Poll Data Sources",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -432,
        496
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 15
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "f6dd7cd9-91e3-4ed0-9251-2d792d23e5b6",
      "name": "Workflow Configuration",
      "type": "n8n-nodes-base.set",
      "position": [
        -176,
        496
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "id-1",
              "name": "socialMediaApiUrl",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__Social Media API endpoint URL__>"
            },
            {
              "id": "id-2",
              "name": "emailApiUrl",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__Email API endpoint URL__>"
            },
            {
              "id": "id-3",
              "name": "supportTicketsApiUrl",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__Support Tickets API endpoint URL__>"
            },
            {
              "id": "id-4",
              "name": "chatApiUrl",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__Chat API endpoint URL__>"
            },
            {
              "id": "id-5",
              "name": "reviewsApiUrl",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__Reviews API endpoint URL__>"
            },
            {
              "id": "id-6",
              "name": "negativeSpikeThreshold",
              "type": "number",
              "value": 0.3
            },
            {
              "id": "id-7",
              "name": "sentimentShiftThreshold",
              "type": "number",
              "value": 0.2
            },
            {
              "id": "id-8",
              "name": "crmApiUrl",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__CRM API endpoint URL__>"
            },
            {
              "id": "id-9",
              "name": "marketingApiUrl",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__Marketing Automation API endpoint URL__>"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "ee55e378-269f-486c-9aac-f858cf5d2780",
      "name": "Fetch Social Media Feedback",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        32,
        416
      ],
      "parameters": {
        "url": "={{ $('Workflow Configuration').first().json.socialMediaApiUrl }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "bd726636-0dca-4348-84b9-3640602ba9db",
      "name": "Fetch Email Feedback",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        32,
        576
      ],
      "parameters": {
        "url": "={{ $('Workflow Configuration').first().json.emailApiUrl }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "4f02edb3-dcee-4293-96b2-10fbd80396c2",
      "name": "Fetch Support Tickets",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        32,
        720
      ],
      "parameters": {
        "url": "={{ $('Workflow Configuration').first().json.supportTicketsApiUrl }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "cd0ceb34-c56f-4767-872c-eb0a97396196",
      "name": "Fetch Chat Transcripts",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        32,
        880
      ],
      "parameters": {
        "url": "={{ $('Workflow Configuration').first().json.chatApiUrl }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "8d1f9ed7-2f45-4b66-9f0f-b0cb2380d3a4",
      "name": "Fetch Product Reviews",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        32,
        1040
      ],
      "parameters": {
        "url": "={{ $('Workflow Configuration').first().json.reviewsApiUrl }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "7abf6341-54f3-44cc-b85d-31454339153c",
      "name": "Merge All Feedback Sources",
      "type": "n8n-nodes-base.merge",
      "position": [
        256,
        544
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineAll"
      },
      "typeVersion": 3.2
    },
    {
      "id": "e0d3b978-b772-4392-852c-b59332fec103",
      "name": "Clean and Normalize Text Data",
      "type": "n8n-nodes-base.code",
      "position": [
        432,
        448
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Clean and normalize text data\nconst item = $input.item.json;\n\n// Function to remove HTML tags\nfunction removeHtmlTags(text) {\n  if (!text) return '';\n  return text.replace(/<[^>]*>/g, '');\n}\n\n// Function to normalize unicode characters\nfunction normalizeUnicode(text) {\n  if (!text) return '';\n  return text.normalize('NFKD');\n}\n\n// Function to remove special characters (keep alphanumeric, spaces, and basic punctuation)\nfunction removeSpecialCharacters(text) {\n  if (!text) return '';\n  return text.replace(/[^a-zA-Z0-9\\s.,!?;:'-]/g, '');\n}\n\n// Function to clean and normalize text\nfunction cleanText(text) {\n  if (!text) return '';\n  \n  // Remove HTML tags\n  let cleaned = removeHtmlTags(text);\n  \n  // Trim whitespace\n  cleaned = cleaned.trim();\n  \n  // Normalize unicode characters\n  cleaned = normalizeUnicode(cleaned);\n  \n  // Remove special characters\n  cleaned = removeSpecialCharacters(cleaned);\n  \n  // Remove extra whitespace (multiple spaces to single space)\n  cleaned = cleaned.replace(/\\s+/g, ' ');\n  \n  // Convert to lowercase for consistency\n  cleaned = cleaned.toLowerCase();\n  \n  return cleaned;\n}\n\n// Apply cleaning to text fields\nconst cleanedItem = {\n  ...item,\n  text_cleaned: cleanText(item.text || item.content || item.message || item.feedback || ''),\n  text_original: item.text || item.content || item.message || item.feedback || ''\n};\n\n// If there's a title or subject field, clean that too\nif (item.title) {\n  cleanedItem.title_cleaned = cleanText(item.title);\n  cleanedItem.title_original = item.title;\n}\n\nif (item.subject) {\n  cleanedItem.subject_cleaned = cleanText(item.subject);\n  cleanedItem.subject_original = item.subject;\n}\n\nreturn cleanedItem;"
      },
      "typeVersion": 2
    },
    {
      "id": "a2baffb2-297c-44ac-9e0d-3a56dd647745",
      "name": "Unify Data Schema",
      "type": "n8n-nodes-base.set",
      "position": [
        592,
        448
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "id-1",
              "name": "feedbackId",
              "type": "string",
              "value": "={{ $now.toMillis() }}-{{ $itemIndex }}"
            },
            {
              "id": "id-2",
              "name": "feedbackText",
              "type": "string",
              "value": "={{ $json.text || $json.content || $json.message || $json.body || $json.feedback }}"
            },
            {
              "id": "id-3",
              "name": "source",
              "type": "string",
              "value": "={{ $json.source || $json.channel || $json.type }}"
            },
            {
              "id": "id-4",
              "name": "timestamp",
              "type": "string",
              "value": "={{ $now.toISO() }}"
            },
            {
              "id": "id-5",
              "name": "customerId",
              "type": "string",
              "value": "={{ $json.customerId || $json.userId || $json.customer_id || $json.user_id }}"
            },
            {
              "id": "id-6",
              "name": "metadata",
              "type": "object",
              "value": "={{ JSON.stringify({ originalSource: $json.source, rawData: $json }) }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "8156aae3-c2eb-4825-a1c9-3d458c52206a",
      "name": "Azure OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatAzureOpenAi",
      "position": [
        832,
        672
      ],
      "parameters": {
        "model": "<__PLACEHOLDER_VALUE__Azure OpenAI deployment name__>",
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "3872eba0-1810-4222-be9a-fba8d45e3c23",
      "name": "Sentiment Analysis - Positive/Neutral/Negative",
      "type": "@n8n/n8n-nodes-langchain.sentimentAnalysis",
      "position": [
        784,
        432
      ],
      "parameters": {
        "options": {},
        "inputText": "={{ $json.feedbackText }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "57bba0f9-d709-4d7e-ba7a-5be2185b079f",
      "name": "OpenAI - Emotion Detection",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        1104,
        416
      ],
      "parameters": {
        "operation": "message"
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "d3565faf-d3e5-4149-a41e-b87d5009dedc",
      "name": "OpenAI - Entity Extraction",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        1408,
        480
      ],
      "parameters": {
        "operation": "message"
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "398a760a-40fc-4885-be53-0d9ee519bb18",
      "name": "Combine Analysis Results",
      "type": "n8n-nodes-base.set",
      "position": [
        1760,
        608
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "id-1",
              "name": "sentiment",
              "type": "string",
              "value": "={{ $('Sentiment Analysis - Positive/Neutral/Negative').item.json.sentiment }}"
            },
            {
              "id": "id-2",
              "name": "emotions",
              "type": "string",
              "value": "={{ $('OpenAI - Emotion Detection').item.json.emotions }}"
            },
            {
              "id": "id-3",
              "name": "entities",
              "type": "string",
              "value": "={{ $('OpenAI - Entity Extraction').item.json.entities }}"
            },
            {
              "id": "id-4",
              "name": "sentimentScore",
              "type": "number",
              "value": "={{ $('Sentiment Analysis - Positive/Neutral/Negative').item.json.sentimentScore }}"
            },
            {
              "id": "id-5",
              "name": "analysisTimestamp",
              "type": "string",
              "value": "={{ $now.toISO() }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "44ab985e-09dc-43f4-9b07-83c02c61d58f",
      "name": "Store Feedback in Database",
      "type": "n8n-nodes-base.postgres",
      "position": [
        1984,
        480
      ],
      "parameters": {
        "table": {
          "__rl": true,
          "mode": "name",
          "value": "customer_feedback"
        },
        "schema": {
          "__rl": true,
          "mode": "list",
          "value": "public"
        },
        "columns": {
          "value": {
            "source": "={{ $json.source }}",
            "emotions": "={{ $json.emotions }}",
            "entities": "={{ $json.entities }}",
            "metadata": "={{ $json.metadata }}",
            "sentiment": "={{ $json.sentiment }}",
            "timestamp": "={{ $json.timestamp }}",
            "customer_id": "={{ $json.customer_id }}",
            "feedback_id": "={{ $json.feedback_id }}",
            "feedback_text": "={{ $json.feedback_text }}",
            "sentiment_score": "={{ $json.sentiment_score }}"
          },
          "mappingMode": "defineBelow"
        },
        "options": {}
      },
      "typeVersion": 2.6
    },
    {
      "id": "5f392f45-b5c8-4e0a-b7b8-c28a9bdb07ca",
      "name": "Calculate Sentiment Trends",
      "type": "n8n-nodes-base.code",
      "position": [
        2208,
        464
      ],
      "parameters": {
        "jsCode": "// Calculate sentiment trends from aggregated feedback data\n// Compute rolling averages, detect spikes, and calculate sentiment distribution\n\nconst items = $input.all();\n\n// Initialize results object\nconst results = {\n  sentimentDistribution: {},\n  rollingAverages: [],\n  spikes: [],\n  trendsBySource: {},\n  trendsByTimePeriod: {}\n};\n\n// Helper function to calculate sentiment score\nfunction getSentimentScore(sentiment) {\n  const scores = {\n    'positive': 1,\n    'neutral': 0,\n    'negative': -1\n  };\n  return scores[sentiment?.toLowerCase()] || 0;\n}\n\n// Process each item\nconst dataPoints = [];\nfor (const item of items) {\n  const data = item.json;\n  const sentimentScore = getSentimentScore(data.sentiment);\n  \n  dataPoints.push({\n    timestamp: data.timestamp || data.created_at || new Date().toISOString(),\n    sentiment: data.sentiment,\n    score: sentimentScore,\n    source: data.source || 'unknown',\n    entity: data.entity || 'general'\n  });\n}\n\n// Sort by timestamp\ndataPoints.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));\n\n// Calculate sentiment distribution\nconst distribution = { positive: 0, neutral: 0, negative: 0 };\nfor (const point of dataPoints) {\n  const sentiment = point.sentiment?.toLowerCase();\n  if (distribution.hasOwnProperty(sentiment)) {\n    distribution[sentiment]++;\n  }\n}\nresults.sentimentDistribution = distribution;\n\n// Calculate rolling averages (7-day window)\nconst windowSize = 7;\nfor (let i = 0; i < dataPoints.length; i++) {\n  const windowStart = Math.max(0, i - windowSize + 1);\n  const window = dataPoints.slice(windowStart, i + 1);\n  const avgScore = window.reduce((sum, p) => sum + p.score, 0) / window.length;\n  \n  results.rollingAverages.push({\n    timestamp: dataPoints[i].timestamp,\n    averageScore: avgScore,\n    windowSize: window.length\n  });\n}\n\n// Detect spikes (significant deviations from rolling average)\nconst spikeThreshold = 0.3;\nfor (let i = windowSize; i < dataPoints.length; i++) {\n  const currentScore = dataPoints[i].score;\n  const avgScore = results.rollingAverages[i].averageScore;\n  const deviation = Math.abs(currentScore - avgScore);\n  \n  if (deviation > spikeThreshold) {\n    results.spikes.push({\n      timestamp: dataPoints[i].timestamp,\n      sentiment: dataPoints[i].sentiment,\n      score: currentScore,\n      averageScore: avgScore,\n      deviation: deviation,\n      source: dataPoints[i].source\n    });\n  }\n}\n\n// Calculate trends by source\nconst sourceGroups = {};\nfor (const point of dataPoints) {\n  if (!sourceGroups[point.source]) {\n    sourceGroups[point.source] = [];\n  }\n  sourceGroups[point.source].push(point.score);\n}\n\nfor (const [source, scores] of Object.entries(sourceGroups)) {\n  const avgScore = scores.reduce((sum, s) => sum + s, 0) / scores.length;\n  results.trendsBySource[source] = {\n    averageScore: avgScore,\n    count: scores.length,\n    positiveRate: scores.filter(s => s > 0).length / scores.length,\n    negativeRate: scores.filter(s => s < 0).length / scores.length\n  };\n}\n\n// Calculate trends by time period (daily)\nconst periodGroups = {};\nfor (const point of dataPoints) {\n  const date = new Date(point.timestamp).toISOString().split('T')[0];\n  if (!periodGroups[date]) {\n    periodGroups[date] = [];\n  }\n  periodGroups[date].push(point.score);\n}\n\nfor (const [date, scores] of Object.entries(periodGroups)) {\n  const avgScore = scores.reduce((sum, s) => sum + s, 0) / scores.length;\n  results.trendsByTimePeriod[date] = {\n    averageScore: avgScore,\n    count: scores.length,\n    positiveCount: scores.filter(s => s > 0).length,\n    neutralCount: scores.filter(s => s === 0).length,\n    negativeCount: scores.filter(s => s < 0).length\n  };\n}\n\n// Return results\nreturn [{ json: results }];"
      },
      "typeVersion": 2
    },
    {
      "id": "06dc9a91-fb43-4c5b-972b-e572e3327272",
      "name": "Aggregate by Entity and Time Period",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        2400,
        464
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData",
        "destinationFieldName": "aggregatedData"
      },
      "typeVersion": 1
    },
    {
      "id": "a5d0a158-fc35-40ff-b300-d895be8f004a",
      "name": "Check for Negative Spike",
      "type": "n8n-nodes-base.if",
      "position": [
        2688,
        288
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "id-1",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $('Aggregate by Entity and Time Period').item.json.negativeSentimentPercentage }}",
              "rightValue": "={{ $('Workflow Configuration').first().json.negativeSpikeThreshold }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "824fb7f6-1507-43ad-b073-fd00d2ea504a",
      "name": "Check for Sudden Sentiment Shift",
      "type": "n8n-nodes-base.if",
      "position": [
        2688,
        448
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "id-1",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $('Aggregate by Entity and Time Period').item.json.sentimentShift }}",
              "rightValue": "={{ $('Workflow Configuration').first().json.sentimentShiftThreshold }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "b51e28a7-1e7a-4c8e-bd41-f1f477f5442d",
      "name": "Send Alert to Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        2960,
        272
      ],
      "parameters": {
        "text": "=\ud83d\udea8 **Sentiment Alert Detected**\n\n**Alert Type:** {{ $json.alertType || 'Sentiment Anomaly' }}\n**Entity Affected:** {{ $json.entity || 'N/A' }}\n**Current Sentiment Score:** {{ $json.sentimentScore || 'N/A' }}\n**Time Period:** {{ $json.timePeriod || 'N/A' }}\n**Threshold Exceeded:** {{ $json.thresholdExceeded || 'N/A' }}\n\nPlease review the dashboard for more details.",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "id",
          "value": "<__PLACEHOLDER_VALUE__Slack channel ID or name__>"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "d6fc4811-7a79-4fa4-bdbc-19bdadf0b2b5",
      "name": "Send Alert Email",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        2960,
        432
      ],
      "parameters": {
        "html": "=<h2>Customer Sentiment Alert</h2>\n<p><strong>Alert Type:</strong> {{ $json.alertType }}</p>\n<p><strong>Timestamp:</strong> {{ $json.timestamp }}</p>\n\n<h3>Sentiment Metrics</h3>\n<ul>\n  <li><strong>Current Sentiment Score:</strong> {{ $json.sentimentScore }}</li>\n  <li><strong>Sentiment Trend:</strong> {{ $json.sentimentTrend }}</li>\n  <li><strong>Negative Feedback Count:</strong> {{ $json.negativeFeedbackCount }}</li>\n  <li><strong>Change Rate:</strong> {{ $json.changeRate }}%</li>\n</ul>\n\n<h3>Affected Entities</h3>\n<p>{{ $json.affectedEntities }}</p>\n\n<h3>Top Issues</h3>\n<p>{{ $json.topIssues }}</p>\n\n<p>Please review the dashboard for detailed analysis and take appropriate action.</p>\n\n<p><em>This alert was generated automatically by the Customer Sentiment Tracking System.</em></p>",
        "options": {
          "appendAttribution": true
        },
        "subject": "=Customer Sentiment Alert: {{ $json.alertType }}",
        "toEmail": "<__PLACEHOLDER_VALUE__Alert recipient email addresses__>",
        "fromEmail": "<__PLACEHOLDER_VALUE__Sender email address__>"
      },
      "typeVersion": 2.1
    },
    {
      "id": "4042811e-c468-4f6d-8a85-3971fee49e68",
      "name": "Update Dashboard - Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2960,
        640
      ],
      "parameters": {
        "columns": {
          "value": {
            "entity": "={{ $json.entity }}",
            "timestamp": "={{ $json.timestamp }}",
            "feedback_count": "={{ $json.feedback_count }}",
            "sentiment_score": "={{ $json.sentiment_score }}",
            "trend_direction": "={{ $json.trend_direction }}",
            "sentiment_distribution": "={{ $json.sentiment_distribution }}"
          },
          "schema": [
            {
              "id": "timestamp",
              "required": false,
              "displayName": "timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "entity",
              "required": false,
              "displayName": "entity",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "sentiment_score",
              "required": false,
              "displayName": "sentiment_score",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            },
            {
              "id": "sentiment_distribution",
              "required": false,
              "displayName": "sentiment_distribution",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            },
            {
              "id": "feedback_count",
              "required": false,
              "displayName": "feedback_count",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            },
            {
              "id": "trend_direction",
              "required": false,
              "displayName": "trend_direction",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "entity"
          ]
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Sentiment Dashboard"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "<__PLACEHOLDER_VALUE__Google Sheets document ID__>"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "5708352b-ae26-4f26-9ee1-129755341f9c",
      "name": "Sync to CRM System",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1984,
        672
      ],
      "parameters": {
        "url": "={{ $('Workflow Configuration').first().json.crmApiUrl }}",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "sendHeaders": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "customer_id",
              "value": "={{ $json.customer_id }}"
            },
            {
              "name": "sentiment",
              "value": "={{ $json.sentiment }}"
            },
            {
              "name": "feedback_summary",
              "value": "={{ $json.feedback_summary }}"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Authorization",
              "value": "={{ $('Workflow Configuration').first().json.crmApiKey }}"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "d8129fc8-b869-4d85-a3ab-c0d087c1fbc9",
      "name": "Sync to Marketing Automation",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1984,
        864
      ],
      "parameters": {
        "url": "={{ $('Workflow Configuration').first().json.marketingApiUrl }}",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "sendHeaders": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "customer_id",
              "value": "={{ $json.customer_id }}"
            },
            {
              "name": "sentiment",
              "value": "={{ $json.sentiment }}"
            },
            {
              "name": "entities",
              "value": "={{ $json.entities }}"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Authorization",
              "value": "Bearer <__PLACEHOLDER_VALUE__Marketing API Token__>"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "b81a1f63-dcf5-4d79-882c-6a61222ca9e7",
      "name": "Generate Insights Report",
      "type": "n8n-nodes-base.code",
      "position": [
        2960,
        864
      ],
      "parameters": {
        "jsCode": "// Generate Comprehensive Insights Report\nconst items = $input.all();\n\nif (!items || items.length === 0) {\n  return [{ json: { error: 'No data available for insights generation' } }];\n}\n\n// Initialize data structures for analysis\nconst sentimentCounts = { positive: 0, neutral: 0, negative: 0 };\nconst entitySentiments = {};\nconst emotionCounts = {};\nconst timeSeriesData = {};\n\n// Process all items\nitems.forEach(item => {\n  const data = item.json;\n  \n  // Count sentiments\n  if (data.sentiment) {\n    const sentiment = data.sentiment.toLowerCase();\n    if (sentimentCounts[sentiment] !== undefined) {\n      sentimentCounts[sentiment]++;\n    }\n  }\n  \n  // Track entity sentiments\n  if (data.entity && data.sentiment) {\n    if (!entitySentiments[data.entity]) {\n      entitySentiments[data.entity] = { positive: 0, neutral: 0, negative: 0, total: 0 };\n    }\n    const sentiment = data.sentiment.toLowerCase();\n    if (entitySentiments[data.entity][sentiment] !== undefined) {\n      entitySentiments[data.entity][sentiment]++;\n    }\n    entitySentiments[data.entity].total++;\n  }\n  \n  // Track emotions\n  if (data.emotion) {\n    emotionCounts[data.emotion] = (emotionCounts[data.emotion] || 0) + 1;\n  }\n  \n  // Track time series (by date)\n  if (data.timestamp || data.date) {\n    const date = new Date(data.timestamp || data.date).toISOString().split('T')[0];\n    if (!timeSeriesData[date]) {\n      timeSeriesData[date] = { positive: 0, neutral: 0, negative: 0, total: 0 };\n    }\n    const sentiment = (data.sentiment || '').toLowerCase();\n    if (timeSeriesData[date][sentiment] !== undefined) {\n      timeSeriesData[date][sentiment]++;\n    }\n    timeSeriesData[date].total++;\n  }\n});\n\n// Calculate top positive entities\nconst topPositiveEntities = Object.entries(entitySentiments)\n  .map(([entity, sentiments]) => ({\n    entity,\n    positiveCount: sentiments.positive,\n    positivePercentage: ((sentiments.positive / sentiments.total) * 100).toFixed(2),\n    totalMentions: sentiments.total\n  }))\n  .sort((a, b) => b.positiveCount - a.positiveCount)\n  .slice(0, 10);\n\n// Calculate top negative entities\nconst topNegativeEntities = Object.entries(entitySentiments)\n  .map(([entity, sentiments]) => ({\n    entity,\n    negativeCount: sentiments.negative,\n    negativePercentage: ((sentiments.negative / sentiments.total) * 100).toFixed(2),\n    totalMentions: sentiments.total\n  }))\n  .sort((a, b) => b.negativeCount - a.negativeCount)\n  .slice(0, 10);\n\n// Calculate overall sentiment distribution\nconst totalFeedback = items.length;\nconst sentimentDistribution = {\n  positive: {\n    count: sentimentCounts.positive,\n    percentage: ((sentimentCounts.positive / totalFeedback) * 100).toFixed(2)\n  },\n  neutral: {\n    count: sentimentCounts.neutral,\n    percentage: ((sentimentCounts.neutral / totalFeedback) * 100).toFixed(2)\n  },\n  negative: {\n    count: sentimentCounts.negative,\n    percentage: ((sentimentCounts.negative / totalFeedback) * 100).toFixed(2)\n  }\n};\n\n// Top emotions\nconst topEmotions = Object.entries(emotionCounts)\n  .map(([emotion, count]) => ({\n    emotion,\n    count,\n    percentage: ((count / totalFeedback) * 100).toFixed(2)\n  }))\n  .sort((a, b) => b.count - a.count)\n  .slice(0, 5);\n\n// Sentiment trends over time\nconst sentimentTrends = Object.entries(timeSeriesData)\n  .map(([date, sentiments]) => ({\n    date,\n    positive: sentiments.positive,\n    neutral: sentiments.neutral,\n    negative: sentiments.negative,\n    total: sentiments.total,\n    sentimentScore: ((sentiments.positive - sentiments.negative) / sentiments.total * 100).toFixed(2)\n  }))\n  .sort((a, b) => new Date(a.date) - new Date(b.date));\n\n// Generate actionable recommendations\nconst recommendations = [];\n\n// Recommendation based on negative sentiment\nif (sentimentDistribution.negative.percentage > 30) {\n  recommendations.push({\n    priority: 'HIGH',\n    category: 'Sentiment Alert',\n    recommendation: `Negative sentiment is at ${sentimentDistribution.negative.percentage}%. Immediate action required to address customer concerns.`,\n    action: 'Review top negative entities and create action plan to address issues'\n  });\n}\n\n// Recommendations for top negative entities\nif (topNegativeEntities.length > 0 && topNegativeEntities[0].negativePercentage > 50) {\n  recommendations.push({\n    priority: 'HIGH',\n    category: 'Entity Focus',\n    recommendation: `Entity \"${topNegativeEntities[0].entity}\" has ${topNegativeEntities[0].negativePercentage}% negative sentiment.`,\n    action: `Investigate issues related to ${topNegativeEntities[0].entity} and implement corrective measures`\n  });\n}\n\n// Positive reinforcement recommendation\nif (topPositiveEntities.length > 0 && topPositiveEntities[0].positivePercentage > 70) {\n  recommendations.push({\n    priority: 'MEDIUM',\n    category: 'Success Story',\n    recommendation: `Entity \"${topPositiveEntities[0].entity}\" has ${topPositiveEntities[0].positivePercentage}% positive sentiment.`,\n    action: `Leverage success of ${topPositiveEntities[0].entity} in marketing and replicate best practices`\n  });\n}\n\n// Emotion-based recommendation\nif (topEmotions.length > 0 && ['anger', 'frustration', 'disappointment'].includes(topEmotions[0].emotion.toLowerCase())) {\n  recommendations.push({\n    priority: 'HIGH',\n    category: 'Emotional Response',\n    recommendation: `Top emotion detected is \"${topEmotions[0].emotion}\" (${topEmotions[0].percentage}%).`,\n    action: 'Prioritize customer support and proactive outreach to address emotional concerns'\n  });\n}\n\n// Trend-based recommendation\nif (sentimentTrends.length >= 2) {\n  const recentTrend = sentimentTrends.slice(-3);\n  const avgRecentScore = recentTrend.reduce((sum, t) => sum + parseFloat(t.sentimentScore), 0) / recentTrend.length;\n  \n  if (avgRecentScore < -10) {\n    recommendations.push({\n      priority: 'HIGH',\n      category: 'Trend Alert',\n      recommendation: `Recent sentiment trend is declining (avg score: ${avgRecentScore.toFixed(2)}).`,\n      action: 'Analyze recent changes in product/service and address emerging issues quickly'\n    });\n  } else if (avgRecentScore > 20) {\n    recommendations.push({\n      priority: 'LOW',\n      category: 'Positive Trend',\n      recommendation: `Recent sentiment trend is improving (avg score: ${avgRecentScore.toFixed(2)}).`,\n      action: 'Continue current strategies and document successful practices'\n    });\n  }\n}\n\n// General recommendation if no specific issues\nif (recommendations.length === 0) {\n  recommendations.push({\n    priority: 'LOW',\n    category: 'Maintenance',\n    recommendation: 'Sentiment levels are stable. Continue monitoring for changes.',\n    action: 'Maintain current customer engagement strategies and monitor trends'\n  });\n}\n\n// Compile comprehensive report\nconst insightsReport = {\n  reportGeneratedAt: new Date().toISOString(),\n  summary: {\n    totalFeedbackAnalyzed: totalFeedback,\n    reportingPeriod: sentimentTrends.length > 0 ? {\n      startDate: sentimentTrends[0].date,\n      endDate: sentimentTrends[sentimentTrends.length - 1].date\n    } : null,\n    overallSentimentScore: ((sentimentCounts.positive - sentimentCounts.negative) / totalFeedback * 100).toFixed(2)\n  },\n  sentimentDistribution,\n  topPositiveEntities,\n  topNegativeEntities,\n  topEmotions,\n  sentimentTrends,\n  recommendations,\n  keyInsights: [\n    `${sentimentDistribution.positive.percentage}% of feedback is positive`,\n    `${sentimentDistribution.negative.percentage}% of feedback is negative`,\n    topPositiveEntities.length > 0 ? `Best performing entity: ${topPositiveEntities[0].entity}` : 'No entity data available',\n    topNegativeEntities.length > 0 ? `Needs attention: ${topNegativeEntities[0].entity}` : 'No negative entities identified',\n    topEmotions.length > 0 ? `Most common emotion: ${topEmotions[0].emotion}` : 'No emotion data available'\n  ]\n};\n\nreturn [{ json: insightsReport }];"
      },
      "typeVersion": 2
    },
    {
      "id": "4b6eb559-8ebd-4a7b-a360-42fd9a5a77e6",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        464,
        -16
      ],
      "parameters": {
        "color": 6,
        "width": 464,
        "height": 208,
        "content": "## Prerequisites\nSocial media/survey API credentials; OpenAI API key; database access; CRM system credentials; email notification setup\n\n## Use Cases\nCustomer sentiment tracking; product feedback aggregation; support ticket prioritization; brand monitoring; trend identification"
      },
      "typeVersion": 1
    },
    {
      "id": "3067a3c7-6d2d-44b1-9eee-b8d389673206",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -32,
        -16
      ],
      "parameters": {
        "width": 448,
        "height": 288,
        "content": "## Setup Instructions\n1. **Data Sources:** Connect social media APIs, survey tools, and customer support platforms.\n2. **AI Analysis:** Configure the OpenAI API with sentiment and theme-extraction prompts.\n3. **Database:** Set up a feedback storage schema in your utility database.\n4. **Alerts:** Configure email notifications and CRM triggers for priority issues.\n5. **Dashboards:** Link your analytics and reporting tools for real-time insights.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "525eda6d-8ab2-4f81-8ba0-f2b710663da8",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -480,
        -16
      ],
      "parameters": {
        "width": 400,
        "height": 272,
        "content": "## How It Works\nScheduled processes retrieve customer feedback from multiple channels. The system performs sentiment analysis to classify tone, then uses OpenAI models to extract themes, topics, and urgency indicators. All processed results are stored in a centralized database for trend tracking. Automated rules identify high-risk or negative sentiment items and trigger alerts to the relevant teams. Dashboards and workflow automation then visualize insights and support follow-up actions.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "079a1521-c017-4968-8376-67672927ea69",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        960,
        -16
      ],
      "parameters": {
        "color": 3,
        "width": 672,
        "content": "## Customization\nAdjust sentiment thresholds; add new feedback sources; modify categorization rules \n\n## Benefits\nReduces analysis time 85%; captures actionable insights; enables rapid response to issues "
      },
      "typeVersion": 1
    },
    {
      "id": "5847bab2-1c95-464a-85b8-7f2af437c7d0",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -496,
        288
      ],
      "parameters": {
        "color": 5,
        "width": 224,
        "height": 464,
        "content": "## Schedule Feedback Cycle\n\nRuns on a set cadence to pull feedback from all channels.\n**Why:** Prevents gaps and surfaces issues early."
      },
      "typeVersion": 1
    },
    {
      "id": "82322b1b-311a-477a-a424-994a913925bb",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -240,
        288
      ],
      "parameters": {
        "color": 5,
        "width": 416,
        "height": 912,
        "content": "## Fetch Social Posts\n\nCollects mentions, reviews, and comments across major platforms.\n**Why:** Captures real-time, unfiltered sentiment."
      },
      "typeVersion": 1
    },
    {
      "id": "e3d1ec93-b11e-4cad-82ce-e66d168ffb0e",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        192,
        288
      ],
      "parameters": {
        "color": 5,
        "width": 192,
        "height": 464,
        "content": "## Aggregate Support Tickets\n\nPulls tickets and resolution notes from helpdesk tools.\n**Why:** Reveals recurring pain points hidden in isolated cases."
      },
      "typeVersion": 1
    },
    {
      "id": "c43d9db3-36dc-4c18-96ae-934381948ae1",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        400,
        288
      ],
      "parameters": {
        "color": 5,
        "width": 320,
        "height": 464,
        "content": "## Parse Product Reviews\n\nCollects ratings and comments from app stores and review sites.\n**Why:** Public reviews influence perception and expose product gaps.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "336a8ef7-b8c7-4dca-bdf1-b501ba34cfd8",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        736,
        288
      ],
      "parameters": {
        "color": 5,
        "width": 624,
        "height": 544,
        "content": "## Analyze Sentiment\n\nClassifies tone across channels with context-aware NLP.\n**Why:** Normalized scoring avoids misreading sarcasm or subtle complaints.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "215a39fc-ca17-4aea-aafe-0d657ad51cfb",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1376,
        288
      ],
      "parameters": {
        "color": 5,
        "width": 336,
        "height": 560,
        "content": "\n## Extract Themes & Topics Sync\n\nIdentifies key subjects: pricing, bugs, features, onboarding, service.\n**Why:** Turns raw sentiment into actionable themes.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "2a23e4b0-3c79-4c8e-b459-31075bd1b82f",
      "name": "Sticky Note10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2160,
        288
      ],
      "parameters": {
        "color": 5,
        "width": 416,
        "height": 336,
        "content": "## Identify Sentiment Trends\n\nTracks mood shifts over time.\n**Why:** Spot brewing issues before churn rises."
      },
      "typeVersion": 1
    },
    {
      "id": "05ebc093-6721-44f3-b1a5-f9f3a0c8ea0c",
      "name": "Sticky Note11",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2592,
        96
      ],
      "parameters": {
        "color": 5,
        "height": 640,
        "content": "## Detect Emerging Issues\n\nFlags sudden spikes or new themes.\n**Why:** Early warning for multi-channel problems.\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "d426dcdc-887a-46c0-a984-429decf29ca7",
      "name": "Sticky Note12",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1728,
        288
      ],
      "parameters": {
        "color": 5,
        "width": 416,
        "height": 752,
        "content": "## Intelligent Feedback Routing\n\nAutomatically directs each feedback type to the right team:\nWhy: Critical issues can happen and need to identify the trends\n"
      },
      "typeVersion": 1
    },
    {
      "id": "2bc42ebc-ad8e-4fd8-96fd-132151db9300",
      "name": "Sticky Note13",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2880,
        96
      ],
      "parameters": {
        "color": 5,
        "width": 272,
        "height": 944,
        "content": "## Insights & Reporting\n\nCentralized feedback intelligence:\nWhy: To  detect sentiment and follow-up with actions\n"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "e471c3d6-927a-4add-88ee-9b7814ae1792",
  "connections": {
    "Unify Data Schema": {
      "main": [
        [
          {
            "node": "Sentiment Analysis - Positive/Neutral/Negative",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Email Feedback": {
      "main": [
        [
          {
            "node": "Merge All Feedback Sources",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Workflow Configuration": {
      "main": [
        [
          {
            "node": "Fetch Social Media Feedback",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch Email Feedback",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch Support Tickets",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch Chat Transcripts",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch Product Reviews",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Azure OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Sentiment Analysis - Positive/Neutral/Negative",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Check for Negative Spike": {
      "main": [
        [
          {
            "node": "Send Alert to Slack",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send Alert Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine Analysis Results": {
      "main": [
        [
          {
            "node": "Store Feedback in Database",
            "type": "main",
            "index": 0
          },
          {
            "node": "Sync to CRM System",
            "type": "main",
            "index": 0
          },
          {
            "node": "Sync to Marketing Automation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Sentiment Trends": {
      "main": [
        [
          {
            "node": "Aggregate by Entity and Time Period",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge All Feedback Sources": {
      "main": [
        [
          {
            "node": "Clean and Normalize Text Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI - Emotion Detection": {
      "main": [
        [
          {
            "node": "OpenAI - Entity Extraction",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI - Entity Extraction": {
      "main": [
        [
          {
            "node": "Combine Analysis Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Feedback in Database": {
      "main": [
        [
          {
            "node": "Calculate Sentiment Trends",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Social Media Feedback": {
      "main": [
        [
          {
            "node": "Merge All Feedback Sources",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clean and Normalize Text Data": {
      "main": [
        [
          {
            "node": "Unify Data Schema",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check for Sudden Sentiment Shift": {
      "main": [
        [
          {
            "node": "Send Alert to Slack",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send Alert Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate by Entity and Time Period": {
      "main": [
        [
          {
            "node": "Check for Negative Spike",
            "type": "main",
            "index": 0
          },
          {
            "node": "Check for Sudden Sentiment Shift",
            "type": "main",
            "index": 0
          },
          {
            "node": "Update Dashboard - Google Sheets",
            "type": "main",
            "index": 0
          },
          {
            "node": "Generate Insights Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger - Poll Data Sources": {
      "main": [
        [
          {
            "node": "Workflow Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sentiment Analysis - Positive/Neutral/Negative": {
      "main": [
        [
          {
            "node": "OpenAI - Emotion Detection",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "OpenAI - Emotion Detection",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "OpenAI - Emotion Detection",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

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

Scheduled processes retrieve customer feedback from multiple channels. The system performs sentiment analysis to classify tone, then uses OpenAI models to extract themes, topics, and urgency indicators. All processed results are stored in a centralized database for trend…

Source: https://n8n.io/workflows/10762/ — original creator credit. Request a take-down →

More Data & Sheets workflows → · Browse all categories →

Related workflows

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

Data & Sheets

This workflow functions as an automated "Chief Wellness Officer," helping HR teams and managers prevent employee burnout before it happens. It aggregates data from communication channels and work tool

HTTP Request, Slack, OpenAI +2
Data & Sheets

This n8n workflow automates the transformation of spreadsheet data into professional charts and graphs using AI-driven analysis. Triggered via Slack, it processes uploaded files (Excel, CSV, Google Sh

Agent, Postgres, HTTP Request +8
Data & Sheets

This workflow is designed for Customer Success Managers, Growth Teams, and SaaS Business Owners who want to proactively reduce churn using AI. It automates the analysis of customer health and the deli

HTTP Request, Postgres, OpenAI +2
Data & Sheets

This workflow transforms raw SaaS metrics into a fully automated Product Health Monitoring & Incident Management system.

Postgres, OpenAI, Slack +2
Data & Sheets

This workflow continuously validates data quality using rules stored in Notion, runs anomaly checks against your SQL database, generates AI-powered diagnostics, and alerts your team only when real iss

Postgres, Notion, OpenAI +3