This workflow corresponds to n8n.io template #10821 — we link there as the canonical source.
This workflow follows the Agent → Error Trigger 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 →
{
"id": "FycdG8VvsWdUUDag",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Monitor and Analyze Facebook Comment Sentiment with GPT-4, Slack, Sheets, and Outlook Reports",
"tags": [],
"nodes": [
{
"id": "e0f900e3-e48a-4795-9bd7-a5ccb42d3aa6",
"name": "Main Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1008,
-1392
],
"parameters": {
"width": 420,
"height": 668,
"content": "## Facebook Sentiment Monitor \u2013 Overview\n\nThis workflow automatically tracks new Facebook posts and comments, analyzes audience sentiment using AI, and routes the results to alerts, logs, and daily reports. It helps teams quickly spot negative engagement and maintain a useful record of how audiences respond over time.\n\n### How it works\n1. A daily trigger fetches recent posts and comments.\n2. The data is cleaned and formatted for analysis.\n3. GPT evaluates sentiment for the post and each comment.\n4. Negative sentiment triggers Slack alerts, while all results are logged to Google Sheets.\n5. A daily HTML email report summarizes sentiment trends.\n6. Errors are captured and reported automatically.\n\n### Setup steps\n- Connect Facebook, OpenAI, Slack, Google Sheets, and Outlook credentials.\n- Set your preferred Slack channel.\n- Run a test execution to confirm access.\n\n### Customization\nAdjust trigger timing, sentiment thresholds, or reporting formats as needed.\n"
},
"typeVersion": 1
},
{
"id": "d8799315-9170-426d-a89e-f8c1647ed494",
"name": "Data Collection Group",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1008,
-688
],
"parameters": {
"color": 2,
"width": 392,
"height": 316,
"content": "## Data Collection\nFetches recent Facebook posts, reactions, and comments on a daily schedule. \nGroups all retrieval steps before processing.\n"
},
"typeVersion": 1
},
{
"id": "1174ab64-c4ef-43fd-9549-083603267c5a",
"name": "AI Analysis Group",
"type": "n8n-nodes-base.stickyNote",
"position": [
-352,
-656
],
"parameters": {
"color": 2,
"width": 464,
"height": 464,
"content": "## AI Sentiment Analysis\nEvaluates sentiment for posts and comments using GPT and outputs structured JSON.\n"
},
"typeVersion": 1
},
{
"id": "3dc35ae3-4a8c-4557-be24-b69cec7508fe",
"name": "Routing Logic",
"type": "n8n-nodes-base.stickyNote",
"position": [
128,
-672
],
"parameters": {
"color": 2,
"width": 280,
"height": 312,
"content": "## Routing Logic\nRoutes negative sentiment to alerts and logs, while positive/neutral flows go to reporting.\n"
},
"typeVersion": 1
},
{
"id": "17ca8b08-897f-42cb-bdea-14cc092a04de",
"name": "Alerts & Reporting",
"type": "n8n-nodes-base.stickyNote",
"position": [
416,
-848
],
"parameters": {
"color": 2,
"width": 480,
"height": 660,
"content": "## Alerts & Logging\nSends Slack alerts for negative sentiment and updates Google Sheets for tracking trends.\n"
},
"typeVersion": 1
},
{
"id": "d8910011-bab9-4cd2-8549-29ec54527fce",
"name": "Error Handling",
"type": "n8n-nodes-base.stickyNote",
"position": [
-960,
144
],
"parameters": {
"color": 2,
"width": 616,
"height": 260,
"content": "## Error Handling\nCaptures workflow failures and sends details to Slack for quick debugging.\n"
},
"typeVersion": 1
},
{
"id": "3ff16e53-59e8-4d1e-ac52-4cd35fb1cdc9",
"name": "Error Handler Trigger",
"type": "n8n-nodes-base.errorTrigger",
"position": [
-784,
256
],
"parameters": {},
"typeVersion": 1
},
{
"id": "e872c20b-75f7-412e-8c8e-3b38d1860eb7",
"name": "Slack: Send Error Alert",
"type": "n8n-nodes-base.slack",
"position": [
-576,
256
],
"parameters": {
"text": "=\u26a0\ufe0f *Error in API Error Catalog Workflow*\n*Node:* {{ $json.node.name }}\n*Message:* {{ $json.error.message }}\n*Time:* {{ $json.timestamp }}",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "id",
"value": "C08358MKDCU"
},
"otherOptions": {}
},
"typeVersion": 2.3
},
{
"id": "53d2eab6-4686-4608-a0c3-17ab64a2f981",
"name": "Fetch Recent Facebook Posts",
"type": "n8n-nodes-base.facebookGraphApi",
"position": [
-752,
-544
],
"parameters": {
"edge": "posts",
"options": {
"fields": {
"field": [
{
"name": "id,message,story,created_time,permalink_url,full_picture,attachments{media_type,media,url,description},shares,likes.limit(100).summary(true){id,name},comments.limit(100).summary(true){id,from,message,created_time,like_count,comment_count,reactions.summary(true)},reactions.limit(100).summary(true){type,id,name},from{id,name,category}"
}
]
}
},
"graphApiVersion": "v23.0"
},
"typeVersion": 1
},
{
"id": "253741a6-d741-410f-8fa9-c93b1bc3588d",
"name": "\ud83d\udcac Agent - Sentiment & Tone Evaluator",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
-224,
-544
],
"parameters": {
"text": "=Analyze the following Facebook post and its audience comments.\n\nPost ID: {{$json.postId}}\nPost Message: \"{{$json.message}}\"\nComments: {{JSON.stringify($json.comments)}}\n\nReturn the result strictly in JSON format:\n\n{\n \"postId\": string,\n \"postMessage\": string,\n \"postSentiment\": {\n \"score\": number,\n \"label\": string,\n \"explanation\": string\n },\n \"comments\": [\n {\n \"commentId\": string,\n \"commentMessage\": string,\n \"score\": number,\n \"label\": string,\n \"explanation\": string\n }\n ],\n \"overallCommentSentiment\": {\n \"score\": number,\n \"label\": string,\n \"explanation\": string\n }\n}\n",
"options": {
"systemMessage": "=You are an AI sentiment analysis model specialized in analyzing Facebook posts and their audience comments.\n\nYour task:\n1. Analyze the emotional tone of the main post message.\n2. Analyze each comment individually.\n3. Compute the overall comment sentiment score that represents the collective audience reaction.\n\nOutput Rules:\n- Return strictly valid JSON.\n- Include these top-level fields:\n - postId (string)\n - postMessage (string)\n - postSentiment (object with score, label, explanation)\n - comments (array of objects with commentId, commentMessage, score, label, explanation)\n - overallCommentSentiment (object with score, label, explanation)\n- \"score\" must be a number between -1.0 (very negative) and +1.0 (very positive).\n- \"label\" must be one of: \"Negative\", \"Neutral\", or \"Positive\".\n- \"explanation\" must briefly describe why that sentiment was assigned.\n\nRules for aggregation:\n- Calculate overallCommentSentiment.score as the average of all comment scores.\n- Determine overallCommentSentiment.label based on this logic:\n - score < -0.3 \u2192 \"Negative\"\n - -0.3 \u2264 score \u2264 0.3 \u2192 \"Neutral\"\n - score > 0.3 \u2192 \"Positive\"\n- Always include a short explanation for the overall sentiment.\n- Do not include any extra commentary or text outside of valid JSON.\n"
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 2.1
},
{
"id": "f489eeba-46f0-47ee-8bee-365d6c51b52e",
"name": "Route Based on Sentiment Score",
"type": "n8n-nodes-base.switch",
"position": [
208,
-544
],
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "7ee8a25a-3c77-4674-8fc3-bb2ceb798661",
"operator": {
"type": "number",
"operation": "lt"
},
"leftValue": "={{ $json.output.overallCommentSentiment.score }}",
"rightValue": -0.4
}
]
}
},
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "ea4a9e85-6731-4c0a-8cc1-c4c6205611be",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $json.output.overallCommentSentiment.score }}",
"rightValue": 0
}
]
}
}
]
},
"options": {}
},
"typeVersion": 3.3
},
{
"id": "4c816578-1372-4441-86ee-e6bfb3ba4cb7",
"name": "Slack Alert - Negative Sentiment",
"type": "n8n-nodes-base.slack",
"position": [
704,
-768
],
"parameters": {
"text": "=\ud83d\udea8 *Negative Sentiment Alert*\n\n\ud83d\udcdd *Post Message:*\n{{ $json.output.postMessage }}\n\n\ud83d\udd17 *Post ID:* `{{ $json.output.postId }}`\n\n\ud83d\ude0a *Post Sentiment:* {{ $json.output.postSentiment.label }} (Score: {{ $json.output.postSentiment.score }})\n_{{ $json.output.postSentiment.explanation }}_\n\n\u26a0\ufe0f *Overall Comment Sentiment:* {{ $json.output.overallCommentSentiment.label }} (Score: {{ $json.output.overallCommentSentiment.score }})\n_{{ $json.output.overallCommentSentiment.explanation }}_\n\n\ud83d\udcac *Comments ({{ $json.output.comments.length }}):*\n\n{{ $json.output.comments.map((comment, index) => {\n const emoji = comment.label === 'Positive' ? '\u2705' : comment.label === 'Negative' ? '\u274c' : '\u2796';\n return `${emoji} *Comment ${index + 1}:*\n\"${comment.commentMessage}\"\n\u2022 Sentiment: ${comment.label} (Score: ${comment.score})\n\u2022 Reason: _${comment.explanation}_\n\u2022 Comment ID: \\`${comment.commentId}\\`\n`;\n}).join('\\n') }}\n\n\ud83c\udfaf *Recommended Actions:*\n\u2022 Review and respond to negative comments\n\u2022 Consider adjusting content strategy\n\u2022 Monitor engagement trends",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "id",
"value": "C08358MKDCU"
},
"otherOptions": {},
"authentication": "oAuth2"
},
"credentials": {
"slackOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 2.3
},
{
"id": "3c2d3443-cb45-48bd-b16f-c8ac444c8e51",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-608,
-656
],
"parameters": {
"color": 2,
"height": 272,
"content": "## Data Formatting\nNormalizes Facebook API data into a consistent structure for downstream AI analysis.\n"
},
"typeVersion": 1
},
{
"id": "0cc0d69f-6365-45ac-a3c6-8a2b4ca81e97",
"name": "LLM - OpenAI GPT-4 Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
-272,
-320
],
"parameters": {
"model": {
"__rl": true,
"mode": "id",
"value": "gpt-4o"
},
"options": {
"temperature": 0.4
}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "e9612735-bc0c-4c97-866c-2f009c67e996",
"name": "Memory Buffer - Session Context",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"position": [
-144,
-320
],
"parameters": {
"sessionKey": "=\"Candidate Screening\"",
"sessionIdType": "customKey"
},
"typeVersion": 1.3
},
{
"id": "eef0d55e-1adf-454e-a319-4ac721c0feb2",
"name": "Structured JSON Parser",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
-16,
-320
],
"parameters": {
"jsonSchemaExample": "{\n \"postId\": \"230476031170639_1153939926828947\",\n \"postMessage\": \"Our team just launched something amazing \ud83d\ude80\",\n \"postSentiment\": {\n \"score\": 0.94,\n \"label\": \"Positive\",\n \"explanation\": \"The post conveys excitement and enthusiasm through upbeat language and emoji.\"\n },\n \"comments\": [\n {\n \"commentId\": \"1153939926828947_12+1234567890\",\n \"commentMessage\": \"This post is amazing.\",\n \"score\": 0.88,\n \"label\": \"Positive\",\n \"explanation\": \"The comment expresses strong admiration and positivity.\"\n },\n {\n \"commentId\": \"1153939926828947_904835525302859\",\n \"commentMessage\": \"amazing view of river and mountain.\",\n \"score\": 0.76,\n \"label\": \"Positive\",\n \"explanation\": \"The comment praises the photo using positive adjectives.\"\n }\n ],\n \"overallCommentSentiment\": {\n \"score\": 0.82,\n \"label\": \"Positive\",\n \"explanation\": \"Most comments show enthusiasm and admiration toward the post.\"\n }\n}\n"
},
"typeVersion": 1.3
},
{
"id": "9f4f35ae-ba39-48cc-9303-0b7b996a23ae",
"name": "Format Facebook Data (Code Node)",
"type": "n8n-nodes-base.code",
"position": [
-496,
-544
],
"parameters": {
"jsCode": "// n8n Code Node - Facebook Posts Data Formatter with Comments\n// Access the input items correctly in n8n\n\nconst items = $input.all();\nconst formattedPosts = [];\n\n// Loop through each input item\nfor (const item of items) {\n // Access the data array - handle both wrapped and unwrapped formats\n let postsData = [];\n \n if (Array.isArray(item.json)) {\n // If json is an array, get the first element's data\n postsData = item.json[0]?.data || [];\n } else if (item.json.data) {\n // If json has a data property directly\n postsData = item.json.data;\n } else if (Array.isArray(item.json)) {\n // If json itself is the data array\n postsData = item.json;\n }\n \n // Process each post\n for (const post of postsData) {\n // Format comments data\n const commentsData = (post.comments?.data || []).map(comment => ({\n commentId: comment.id || '',\n commentMessage: comment.message || '',\n commentCreatedTime: comment.created_time || '',\n commentLikeCount: comment.like_count || 0,\n commentCount: comment.comment_count || 0,\n commentor: {\n id: comment.from?.id || '',\n name: comment.from?.name || ''\n },\n commentReactions: {\n count: comment.reactions?.summary?.total_count || 0,\n viewerReaction: comment.reactions?.summary?.viewer_reaction || 'NONE'\n }\n }));\n \n const formattedPost = {\n // Basic Post Information\n postId: post.id || '',\n message: post.message || '',\n createdTime: post.created_time || '',\n permalinkUrl: post.permalink_url || '',\n \n // Page Information\n pageInfo: {\n pageId: post.from?.id || '',\n pageName: post.from?.name || '',\n pageCategory: post.from?.category || ''\n },\n \n // Media Information\n media: {\n hasMedia: (post.attachments?.data?.length || 0) > 0,\n fullPictureUrl: post.full_picture || '',\n attachments: (post.attachments?.data || []).map(att => ({\n mediaType: att.media_type || '',\n url: att.url || '',\n description: att.description || '',\n imageUrl: att.media?.image?.src || '',\n imageWidth: att.media?.image?.width || 0,\n imageHeight: att.media?.image?.height || 0\n }))\n },\n \n // Engagement Metrics\n engagement: {\n likes: {\n count: post.likes?.summary?.total_count || 0,\n canLike: post.likes?.summary?.can_like || false,\n hasLiked: post.likes?.summary?.has_liked || false\n },\n comments: {\n count: post.comments?.summary?.total_count || 0,\n canComment: post.comments?.summary?.can_comment || false\n },\n reactions: {\n count: post.reactions?.summary?.total_count || 0,\n viewerReaction: post.reactions?.summary?.viewer_reaction || 'NONE'\n }\n },\n \n // Comments Array - DETAILED COMMENT DATA\n comments: commentsData,\n \n // Metadata\n metadata: {\n hasComments: (post.comments?.summary?.total_count || 0) > 0,\n hasLikes: (post.likes?.summary?.total_count || 0) > 0,\n hasReactions: (post.reactions?.summary?.total_count || 0) > 0,\n totalEngagement: (post.likes?.summary?.total_count || 0) + \n (post.comments?.summary?.total_count || 0) + \n (post.reactions?.summary?.total_count || 0),\n totalComments: commentsData.length\n }\n };\n \n formattedPosts.push(formattedPost);\n }\n}\n\n// Return the formatted posts in n8n format\nreturn formattedPosts.map(post => ({ json: post }));"
},
"typeVersion": 2
},
{
"id": "76ab3da1-46ca-4d90-833d-a6c1faabcf7d",
"name": "Trigger - Daily Sentiment Analysis",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-992,
-544
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 10
}
]
}
},
"typeVersion": 1.2
},
{
"id": "37e98fd9-1a99-4c03-84e4-0ba922727c86",
"name": "Format HTML Email Report (Code Node)",
"type": "n8n-nodes-base.code",
"position": [
432,
-352
],
"parameters": {
"jsCode": "// n8n Code Node - Format Sentiment Analysis Data for Email\n\nconst data = $input.all();\n\n// Helper function to get sentiment color\nfunction getSentimentColor(label) {\n const colors = {\n 'Positive': '#10b981',\n 'Negative': '#ef4444',\n 'Neutral': '#6b7280'\n };\n return colors[label] || '#6b7280';\n}\n\n// Helper function to get emoji for sentiment\nfunction getSentimentEmoji(label) {\n const emojis = {\n 'Positive': '\ud83d\ude0a',\n 'Negative': '\ud83d\ude1e',\n 'Neutral': '\ud83d\ude10'\n };\n return emojis[label] || '\ud83d\ude10';\n}\n\n// Process each item\nconst emailData = data.map(item => {\n const output = item.json.output;\n \n // Build HTML for comments\n let commentsHtml = '';\n if (output.comments && output.comments.length > 0) {\n commentsHtml = output.comments.map(comment => `\n <div style=\"background-color: #f9fafb; border-left: 4px solid ${getSentimentColor(comment.label)}; padding: 15px; margin: 10px 0; border-radius: 5px;\">\n <p style=\"margin: 0 0 10px 0; color: #374151; font-size: 14px;\">${comment.commentMessage}</p>\n <div style=\"display: flex; align-items: center; gap: 10px;\">\n <span style=\"background-color: ${getSentimentColor(comment.label)}; color: white; padding: 4px 12px; border-radius: 12px; font-size: 12px; font-weight: 600;\">\n ${getSentimentEmoji(comment.label)} ${comment.label}\n </span>\n <span style=\"color: #6b7280; font-size: 12px;\">Score: ${(comment.score * 100).toFixed(0)}%</span>\n </div>\n <p style=\"margin: 10px 0 0 0; color: #6b7280; font-size: 12px; font-style: italic;\">${comment.explanation}</p>\n </div>\n `).join('');\n } else {\n commentsHtml = '<p style=\"color: #6b7280; font-style: italic;\">No comments yet.</p>';\n }\n \n // Build complete HTML email\n const htmlBody = `\n <!DOCTYPE html>\n <html>\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n </head>\n <body style=\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; line-height: 1.6; color: #1f2937; background-color: #f3f4f6; margin: 0; padding: 20px;\">\n <div style=\"max-width: 650px; margin: 0 auto; background-color: white; border-radius: 10px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); overflow: hidden;\">\n \n <div style=\"background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 30px; text-align: center;\">\n <h1 style=\"color: white; margin: 0; font-size: 24px;\">\ud83d\udcca Social Media Sentiment Report</h1>\n </div>\n \n <div style=\"padding: 30px;\">\n \n <div style=\"margin-bottom: 30px;\">\n <h2 style=\"color: #1f2937; font-size: 20px; margin: 0 0 15px 0; border-bottom: 2px solid #e5e7eb; padding-bottom: 10px;\">\n \ud83d\udcdd Post Analysis\n </h2>\n \n <div style=\"background-color: #f9fafb; padding: 20px; border-radius: 8px; border: 1px solid #e5e7eb;\">\n <p style=\"margin: 0 0 15px 0; font-size: 16px; color: #374151; font-weight: 500;\">${output.postMessage}</p>\n \n <div style=\"display: flex; align-items: center; gap: 15px; flex-wrap: wrap;\">\n <span style=\"background-color: ${getSentimentColor(output.postSentiment.label)}; color: white; padding: 6px 16px; border-radius: 20px; font-size: 14px; font-weight: 600;\">\n ${getSentimentEmoji(output.postSentiment.label)} ${output.postSentiment.label}\n </span>\n <span style=\"color: #6b7280; font-size: 14px;\">\n <strong>Confidence:</strong> ${(output.postSentiment.score * 100).toFixed(0)}%\n </span>\n </div>\n \n <p style=\"margin: 15px 0 0 0; color: #6b7280; font-size: 13px; font-style: italic; border-top: 1px solid #e5e7eb; padding-top: 15px;\">\n ${output.postSentiment.explanation}\n </p>\n </div>\n \n <p style=\"margin: 10px 0 0 0; color: #9ca3af; font-size: 12px;\">\n <strong>Post ID:</strong> ${output.postId}\n </p>\n </div>\n \n <div style=\"margin-bottom: 30px;\">\n <h2 style=\"color: #1f2937; font-size: 20px; margin: 0 0 15px 0; border-bottom: 2px solid #e5e7eb; padding-bottom: 10px;\">\n \ud83d\udcac Comments (${output.comments ? output.comments.length : 0})\n </h2>\n ${commentsHtml}\n </div>\n \n <div style=\"background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%); padding: 20px; border-radius: 8px; border: 2px solid ${getSentimentColor(output.overallCommentSentiment.label)};\">\n <h3 style=\"color: #1f2937; font-size: 18px; margin: 0 0 15px 0;\">\n \ud83d\udcc8 Overall Comment Sentiment\n </h3>\n \n <div style=\"display: flex; align-items: center; gap: 15px; margin-bottom: 15px; flex-wrap: wrap;\">\n <span style=\"background-color: ${getSentimentColor(output.overallCommentSentiment.label)}; color: white; padding: 8px 20px; border-radius: 20px; font-size: 16px; font-weight: 600;\">\n ${getSentimentEmoji(output.overallCommentSentiment.label)} ${output.overallCommentSentiment.label}\n </span>\n <span style=\"color: #374151; font-size: 16px; font-weight: 500;\">\n Score: ${(output.overallCommentSentiment.score * 100).toFixed(0)}%\n </span>\n </div>\n \n <p style=\"margin: 0; color: #4b5563; font-size: 14px;\">\n ${output.overallCommentSentiment.explanation}\n </p>\n </div>\n \n </div>\n \n <div style=\"background-color: #f9fafb; padding: 20px; text-align: center; border-top: 1px solid #e5e7eb;\">\n <p style=\"margin: 0; color: #6b7280; font-size: 12px;\">\n Generated by n8n Sentiment Analysis Workflow | ${new Date().toLocaleString()}\n </p>\n </div>\n \n </div>\n </body>\n </html>\n `;\n \n // Generate subject line\n const subject = 'Sentiment Report: ' + output.postSentiment.label + ' Post with ' + (output.comments ? output.comments.length : 0) + ' Comments';\n \n return {\n json: {\n subject: subject,\n htmlBody: htmlBody,\n postId: output.postId,\n postSentiment: output.postSentiment.label,\n commentCount: output.comments ? output.comments.length : 0,\n overallSentiment: output.overallCommentSentiment.label\n }\n };\n});\n\nreturn emailData;"
},
"typeVersion": 2
},
{
"id": "ab4f20d4-c34b-4529-bb98-f11b6554524a",
"name": "Prepare Data for Google Sheets",
"type": "n8n-nodes-base.code",
"position": [
432,
-544
],
"parameters": {
"jsCode": "// n8n Code Node - Format Sentiment Data for Google Sheets\n// This flattens the nested sentiment data into sheet-friendly rows\n\nconst items = $input.all();\nconst sheetRows = [];\n\nfor (const item of items) {\n const data = item.json.output || item.json;\n \n // Create main post row\n const postRow = {\n // Post Information\n postId: data.postId || '',\n postMessage: data.postMessage || '',\n \n // Post Sentiment\n postSentimentScore: data.postSentiment?.score || 0,\n postSentimentLabel: data.postSentiment?.label || '',\n postSentimentExplanation: data.postSentiment?.explanation || '',\n \n // Overall Comment Sentiment\n overallCommentScore: data.overallCommentSentiment?.score || 0,\n overallCommentLabel: data.overallCommentSentiment?.label || '',\n overallCommentExplanation: data.overallCommentSentiment?.explanation || '',\n \n // Comment Counts\n totalComments: data.comments?.length || 0,\n positiveComments: data.comments?.filter(c => c.label === 'Positive').length || 0,\n negativeComments: data.comments?.filter(c => c.label === 'Negative').length || 0,\n neutralComments: data.comments?.filter(c => c.label === 'Neutral').length || 0,\n \n // Timestamp\n analyzedAt: new Date().toISOString(),\n \n // Alert Flag\n needsAttention: data.overallCommentSentiment?.label === 'Negative' || \n data.overallCommentSentiment?.score < -0.3,\n \n // Comments Details (JSON string for reference)\n commentsJson: JSON.stringify(data.comments || [])\n };\n \n sheetRows.push(postRow);\n}\n\n// Return formatted data for Google Sheets\nreturn sheetRows.map(row => ({ json: row }));"
},
"typeVersion": 2
},
{
"id": "31a51d00-433e-400a-b4cd-3972461093bc",
"name": "Update Google Sheet - Sentiment Log",
"type": "n8n-nodes-base.googleSheets",
"position": [
656,
-544
],
"parameters": {
"columns": {
"value": {},
"mappingMode": "defineBelow"
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1pCLHkzcZy42HCL9mAh8bOBQ6wN5PcxnfEXeR6rPUKVg/edit?gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "1pCLHkzcZy42HCL9mAh8bOBQ6wN5PcxnfEXeR6rPUKVg",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1pCLHkzcZy42HCL9mAh8bOBQ6wN5PcxnfEXeR6rPUKVg/edit?gid=0"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "db65a4a0-c870-4f2a-bcd8-688d2e1e0cb2",
"name": "Send Sentiment Report (Outlook)",
"type": "n8n-nodes-base.microsoftOutlook",
"position": [
656,
-352
],
"parameters": {
"subject": "={{ $json.subject }}",
"bodyContent": "={{ $json.htmlBody }}",
"additionalFields": {
"importance": "High",
"bodyContentType": "html"
}
},
"credentials": {
"microsoftOutlookOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 2
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "160ea59e-1849-484d-9696-dd0c9a479352",
"connections": {
"Error Handler Trigger": {
"main": [
[
{
"node": "Slack: Send Error Alert",
"type": "main",
"index": 0
}
]
]
},
"Structured JSON Parser": {
"ai_outputParser": [
[
{
"node": "\ud83d\udcac Agent - Sentiment & Tone Evaluator",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"LLM - OpenAI GPT-4 Model": {
"ai_languageModel": [
[
{
"node": "\ud83d\udcac Agent - Sentiment & Tone Evaluator",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Fetch Recent Facebook Posts": {
"main": [
[
{
"node": "Format Facebook Data (Code Node)",
"type": "main",
"index": 0
}
]
]
},
"Prepare Data for Google Sheets": {
"main": [
[
{
"node": "Update Google Sheet - Sentiment Log",
"type": "main",
"index": 0
}
]
]
},
"Route Based on Sentiment Score": {
"main": [
[
{
"node": "Slack Alert - Negative Sentiment",
"type": "main",
"index": 0
},
{
"node": "Prepare Data for Google Sheets",
"type": "main",
"index": 0
}
],
[
{
"node": "Format HTML Email Report (Code Node)",
"type": "main",
"index": 0
}
]
]
},
"Memory Buffer - Session Context": {
"ai_memory": [
[
{
"node": "\ud83d\udcac Agent - Sentiment & Tone Evaluator",
"type": "ai_memory",
"index": 0
}
]
]
},
"Format Facebook Data (Code Node)": {
"main": [
[
{
"node": "\ud83d\udcac Agent - Sentiment & Tone Evaluator",
"type": "main",
"index": 0
}
]
]
},
"Trigger - Daily Sentiment Analysis": {
"main": [
[
{
"node": "Fetch Recent Facebook Posts",
"type": "main",
"index": 0
}
]
]
},
"Format HTML Email Report (Code Node)": {
"main": [
[
{
"node": "Send Sentiment Report (Outlook)",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udcac Agent - Sentiment & Tone Evaluator": {
"main": [
[
{
"node": "Route Based on Sentiment Score",
"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.
googleSheetsOAuth2ApimicrosoftOutlookOAuth2ApiopenAiApislackOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Automatically analyze the sentiment of Facebook posts and their audience comments using GPT-4 to identify trends and potential PR risks. 🧠💬 This workflow fetches recent posts via the Facebook Graph API, performs AI-powered sentiment analysis on both posts and comments, routes…
Source: https://n8n.io/workflows/10821/ — 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.
Streamline customer support with a real-time, AI-powered answer engine that detects incoming support emails, classifies intent, identifies the customer’s GEO region, and generates a tailored reply rea
Automatically detect, classify, and document GitHub API errors using AI. This workflow connects GitHub, OpenAI (GPT-4o), Airtable, Notion, and Slack to build a real-time, searchable API error knowledg
Streamline Facebook Messenger inbox management with an AI-powered categorization and response system. 💬⚙️ This workflow automatically classifies new messages as Lead, Query, or Spam using GPT-4, route
Automate customer feedback analysis and action planning by integrating Monday.com, Azure OpenAI, Jira, Google Sheets, and Outlook. This workflow classifies customer feedback with AI, calculates busine
Streamline your HR recruitment process with this intelligent automation that reads candidate emails and resumes, analyzes them using GPT-4, and automatically shortlists or rejects applicants based on