AutomationFlowsAI & RAG › Track Positive Instagram Comment Sentiment with Openai, Google Sheets, and Gmail

Track Positive Instagram Comment Sentiment with Openai, Google Sheets, and Gmail

ByisaWOW @isawow on n8n.io

This workflow runs twice daily to fetch recent Instagram post comments via the Instagram Graph API, uses OpenAI to classify sentiment, saves new positive comments to Google Sheets, and emails an HTML digest of the day’s positive comments to your team using Gmail. Runs every day…

Cron / scheduled trigger★★★★☆ complexity25 nodesHTTP RequestGoogle SheetsGmail
AI & RAG Trigger: Cron / scheduled Nodes: 25 Complexity: ★★★★☆ Added:

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

This workflow follows the Gmail → 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": "BYmB3zmma6QWTaE4",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Instagram Comments Sentiment Tracker - Schedule + Graph API + ChatGPT AI + Sheets + Gmail Digest",
  "tags": [],
  "nodes": [
    {
      "id": "bff848f0-1e70-4c7a-a7b6-9af3d77b1be9",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -800,
        -608
      ],
      "parameters": {
        "width": 604,
        "height": 1120,
        "content": "## Instagram Comments Sentiment Tracker\n\nTrack what customers say about you on Instagram without reading every comment yourself. Each morning this workflow collects comments from your latest posts, an OpenAI model rates the sentiment of each one, and only the positive comments are saved to Google Sheets. In the evening your team receives one email digest with the day's best customer feedback.\n\n### How it works\n1. A **Schedule Trigger** runs daily at 9 AM and loads your settings from the **Config** node.\n2. The **Instagram Graph API** returns your last 5 posts and up to 30 comments per post, processed one at a time.\n3. An **OpenAI** model classifies each comment as positive, neutral or negative with a confidence score.\n4. Only positive comments pass the filter. A **Google Sheets** lookup skips comments that were already saved, so repeat runs never create duplicates.\n5. A second trigger at 8 PM reads today's saved comments and sends an HTML digest through **Gmail**.\n\n### Setup steps\n1. Add your Instagram Business Account ID and access token in the **Config** node. You need a Business account linked to a Facebook Page with instagram_basic, instagram_manage_comments and pages_read_engagement permissions.\n2. Connect your OpenAI credential in the sentiment analysis node.\n3. Create a Google Sheet with a tab named Positive Comments and the eight columns used in the Save node, then replace YOUR_INSTAGRAM_TRACKER_SHEET_ID in the three Sheets nodes.\n4. Connect Gmail, replace YOUR_TEAM_EMAIL in the digest node, and activate the workflow.\n\n### Customization\nChange the post and comment limits in the Config node, move the trigger times, or edit the prompt to track negative comments for complaint monitoring instead."
      },
      "typeVersion": 1
    },
    {
      "id": "d58c5c9c-03db-45eb-b919-8262af3c8188",
      "name": "Section - Schedule and Config",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -112,
        -464
      ],
      "parameters": {
        "color": 7,
        "width": 512,
        "height": 768,
        "content": "## 1. Schedule and config\nRuns every day at 9 AM. All Instagram settings live in one Config node, so you only edit values here."
      },
      "typeVersion": 1
    },
    {
      "id": "1ee979cf-c634-4440-9660-446e28106a53",
      "name": "Section - Fetch Posts and Comments",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        464,
        -432
      ],
      "parameters": {
        "color": 7,
        "width": 1480,
        "height": 576,
        "content": "## 2. Fetch posts and comments\nGets the last 5 posts, then loops through each post and each comment one at a time."
      },
      "typeVersion": 1
    },
    {
      "id": "8189bee5-fb00-4149-9288-05eb8745d04b",
      "name": "Section - AI Sentiment Filter",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2000,
        -512
      ],
      "parameters": {
        "color": 7,
        "width": 708,
        "height": 976,
        "content": "## 3. AI sentiment filter\nAn OpenAI model rates every comment. Only positive comments with real text move ahead, the rest return to the loop."
      },
      "typeVersion": 1
    },
    {
      "id": "11012239-acf3-4d80-b905-c9e6e4f68c41",
      "name": "Section - Check Duplicates and Save",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2912,
        -576
      ],
      "parameters": {
        "color": 7,
        "width": 960,
        "height": 1008,
        "content": "## 4. Check duplicates and save\nLooks up the comment ID in the sheet and appends only new positive comments."
      },
      "typeVersion": 1
    },
    {
      "id": "3ba699bc-82b1-411b-9c84-1d8f035f7ee1",
      "name": "Section - Daily Digest Email",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        576
      ],
      "parameters": {
        "color": 7,
        "width": 990,
        "height": 544,
        "content": "## 5. Daily digest email\nA separate 8 PM trigger reads today's saved comments and emails a summary to your team."
      },
      "typeVersion": 1
    },
    {
      "id": "d1ba88bc-35d0-49cc-b23b-201940b9a92c",
      "name": "1. Schedule - Daily 9 AM Comment Fetch",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -48,
        -128
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 9
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "f63848bc-a455-4731-87fe-e1441cfbe9cc",
      "name": "2. Code - Set Config and Credentials",
      "type": "n8n-nodes-base.code",
      "position": [
        208,
        -128
      ],
      "parameters": {
        "jsCode": "// Central config - change Instagram credentials here only\n// Replace these two values before activating the workflow\n\nconst config = {\n  instagramAccountId: 'YOUR_INSTAGRAM_BUSINESS_ACCOUNT_ID',\n  accessToken:        'YOUR_INSTAGRAM_ACCESS_TOKEN',\n  postsToFetch:       5,\n  commentsPerPost:    30,\n  todayDate:          new Date().toISOString().split('T')[0]\n};\n\nreturn [{ json: config }];"
      },
      "typeVersion": 2
    },
    {
      "id": "784cabfe-d036-4a6c-918a-6fef556e2f66",
      "name": "3. HTTP - Fetch Recent Instagram Posts",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        448,
        -128
      ],
      "parameters": {
        "url": "=https://graph.facebook.com/v19.0/{{ $json.instagramAccountId }}/media",
        "options": {
          "timeout": 30000
        },
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "fields",
              "value": "id,caption,permalink,timestamp,media_type"
            },
            {
              "name": "limit",
              "value": "={{ $json.postsToFetch }}"
            },
            {
              "name": "access_token",
              "value": "={{ $json.accessToken }}"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "93b6fa01-b8a1-4a2c-b8a3-2393fa677b1c",
      "name": "4. Code - Extract Posts Array",
      "type": "n8n-nodes-base.code",
      "position": [
        688,
        -128
      ],
      "parameters": {
        "jsCode": "// Extract posts array and attach config fields for downstream use\nconst config  = $('2. Code - Set Config and Credentials').first().json;\nconst apiResp = $json;\n\nconst posts = apiResp.data || [];\n\nif (posts.length === 0) {\n  throw new Error('No Instagram posts found. Check your account ID and access token.');\n}\n\nreturn posts.map(function(post) {\n  return {\n    json: {\n      postId:      post.id,\n      caption:     (post.caption || '').substring(0, 200),\n      permalink:   post.permalink || '',\n      timestamp:   post.timestamp || '',\n      mediaType:   post.media_type || '',\n      accessToken: config.accessToken,\n      commentsPerPost: config.commentsPerPost,\n      todayDate:   config.todayDate\n    }\n  };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "e5a086f7-f30e-40e5-8acd-6c5f34df4c15",
      "name": "5. Loop - Process Each Post",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        928,
        -128
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "2d5e3f0f-c401-47f8-9be8-b4993fcf78dc",
      "name": "6. HTTP - Fetch Comments for This Post",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1168,
        -128
      ],
      "parameters": {
        "url": "=https://graph.facebook.com/v19.0/{{ $json.postId }}/comments",
        "options": {
          "timeout": 30000
        },
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "fields",
              "value": "id,text,username,timestamp"
            },
            {
              "name": "limit",
              "value": "={{ $json.commentsPerPost }}"
            },
            {
              "name": "access_token",
              "value": "={{ $json.accessToken }}"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "21d604f2-4e5c-4c5d-8053-7b7501232a56",
      "name": "7. Code - Extract Comments Array",
      "type": "n8n-nodes-base.code",
      "position": [
        1408,
        -128
      ],
      "parameters": {
        "jsCode": "// Extract comments array for this post\n// Attach post context to each comment for later use\nconst postCtx   = $('5. Loop - Process Each Post').first().json;\nconst apiResp   = $json;\nconst comments  = apiResp.data || [];\n\nif (comments.length === 0) {\n  // No comments on this post - return a marker item with empty text\n  // so the comment loop still runs and hands control back to the post loop\n  return [{\n    json: {\n      commentId:   'none_' + postCtx.postId,\n      commentText: '',\n      username:    'none',\n      commentAt:   '',\n      postId:      postCtx.postId,\n      postUrl:     postCtx.permalink,\n      accessToken: postCtx.accessToken,\n      todayDate:   postCtx.todayDate\n    }\n  }];\n}\n\nreturn comments.map(function(c) {\n  return {\n    json: {\n      commentId:   c.id,\n      commentText: (c.text || '').trim(),\n      username:    c.username || 'unknown',\n      commentAt:   c.timestamp || '',\n      postId:      postCtx.postId,\n      postUrl:     postCtx.permalink,\n      accessToken: postCtx.accessToken,\n      todayDate:   postCtx.todayDate\n    }\n  };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "218abfb2-d9dd-4261-bf00-7de006235924",
      "name": "8. Loop - Process Each Comment",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        1648,
        -128
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "509375c8-58f0-4b74-97c3-0336038ea5ab",
      "name": "9. HTTP - AI Sentiment Analysis",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2032,
        -128
      ],
      "parameters": {
        "url": "https://api.openai.com/v1/chat/completions",
        "method": "POST",
        "options": {
          "timeout": 20000
        },
        "jsonBody": "={\n  \"model\": \"gpt-4o-mini\",\n  \"temperature\": 0,\n  \"max_tokens\": 60,\n  \"messages\": [\n    {\n      \"role\": \"system\",\n      \"content\": \"You are a social media sentiment classifier. Classify the following Instagram comment as POSITIVE, NEUTRAL, or NEGATIVE. POSITIVE means the commenter is happy, appreciative, excited, or saying something nice. NEGATIVE means complaints, anger, frustration, or rude language. NEUTRAL means questions, general observations, or unclear intent. Reply with ONLY a JSON object like this: {\\\"sentiment\\\": \\\"POSITIVE\\\", \\\"score\\\": 0.92, \\\"reason\\\": \\\"one short phrase explaining why\\\"}. Nothing else.\"\n    },\n    {\n      \"role\": \"user\",\n      \"content\": {{ JSON.stringify($json.commentText || 'empty comment') }}\n    }\n  ]\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "openAiApi"
      },
      "typeVersion": 4.3
    },
    {
      "id": "9a1d1257-6c72-4fb5-a68c-2e5c0e7df728",
      "name": "10. Code - Parse Sentiment Label",
      "type": "n8n-nodes-base.code",
      "position": [
        2272,
        -128
      ],
      "parameters": {
        "jsCode": "// Parse the AI sentiment result and merge with comment context\nconst aiResp     = $json;\nconst commentCtx = $('8. Loop - Process Each Comment').first().json;\n\nconst raw = aiResp?.choices?.[0]?.message?.content || '';\n\nlet sentiment = 'NEUTRAL';\nlet score     = 0.5;\nlet reason    = '';\n\ntry {\n  const cleaned = raw.trim()\n    .replace(/^```json\\s*/i, '')\n    .replace(/^```\\s*/i, '')\n    .replace(/```$/i, '')\n    .trim();\n  const parsed  = JSON.parse(cleaned);\n  sentiment     = (parsed.sentiment || 'NEUTRAL').toUpperCase();\n  score         = parseFloat(parsed.score) || 0.5;\n  reason        = parsed.reason || '';\n} catch(e) {\n  // If parse fails, default to NEUTRAL so we skip it\n  sentiment = 'NEUTRAL';\n}\n\n// Validate sentiment label\nif (!['POSITIVE','NEUTRAL','NEGATIVE'].includes(sentiment)) {\n  sentiment = 'NEUTRAL';\n}\n\nreturn [{\n  json: {\n    commentId:    commentCtx.commentId,\n    commentText:  commentCtx.commentText,\n    username:     commentCtx.username,\n    commentAt:    commentCtx.commentAt,\n    postId:       commentCtx.postId,\n    postUrl:      commentCtx.postUrl,\n    todayDate:    commentCtx.todayDate,\n    sentiment,\n    score,\n    reason\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "83412a0e-99e0-4f01-91d7-c9309b0ed1db",
      "name": "11. IF - Is Positive Sentiment?",
      "type": "n8n-nodes-base.if",
      "position": [
        2512,
        -128
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-positive",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.sentiment }}",
              "rightValue": "POSITIVE"
            },
            {
              "id": "cond-has-text",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.commentText }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "e56a0735-c280-4f91-8e3c-e47135abe09e",
      "name": "12. Sheets - Check for Duplicate Comment ID",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2960,
        -288
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ $json.commentId }}",
              "lookupColumn": "Comment ID"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Positive Comments"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_INSTAGRAM_TRACKER_SHEET_ID"
        }
      },
      "typeVersion": 4.5,
      "alwaysOutputData": true
    },
    {
      "id": "88a8557f-d2eb-4675-89cc-83c03c868bc2",
      "name": "13. Code - Check Duplicate Result",
      "type": "n8n-nodes-base.code",
      "position": [
        3200,
        -288
      ],
      "parameters": {
        "jsCode": "// Check if this comment ID already exists in the sheet\n// The Sheets lookup returns matching rows. With Always Output Data on,\n// an empty result still comes through as one item with an empty json object.\nconst items      = $input.all();\nconst commentCtx = $('10. Code - Parse Sentiment Label').first().json;\n\nconst isDuplicate = items.some(function(item) {\n  return item.json && item.json['Comment ID'];\n});\n\nreturn [{\n  json: {\n    ...commentCtx,\n    isDuplicate\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "b94419d4-31e6-4b54-b4ef-6d3b567cfb1e",
      "name": "14. IF - Is New Comment?",
      "type": "n8n-nodes-base.if",
      "position": [
        3440,
        -288
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-new",
              "operator": {
                "type": "boolean",
                "operation": "false",
                "singleValue": true
              },
              "leftValue": "={{ $json.isDuplicate }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "634eeb9c-5334-4960-b755-2599d8857cf4",
      "name": "15. Sheets - Save Positive Comment",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        3680,
        -288
      ],
      "parameters": {
        "columns": {
          "value": {
            "Date": "={{ $json.todayDate }}",
            "Post ID": "={{ $json.postId }}",
            "Post URL": "={{ $json.postUrl }}",
            "Saved At": "={{ new Date().toISOString() }}",
            "Username": "={{ $json.username }}",
            "Comment ID": "={{ $json.commentId }}",
            "Comment Text": "={{ $json.commentText }}",
            "Sentiment Score": "={{ $json.score }}"
          },
          "schema": [
            {
              "id": "Date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Post ID",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Post ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Comment ID",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Comment ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Username",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Username",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Comment Text",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Comment Text",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Sentiment Score",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Sentiment Score",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Post URL",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Post URL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Saved At",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Saved At",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Positive Comments"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_INSTAGRAM_TRACKER_SHEET_ID"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "b5b5e8f6-f330-45f1-994a-26beb23b7aea",
      "name": "16. Schedule - Daily 8 PM Digest Email",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        80,
        768
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 20
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "d8e462b2-253e-47ad-bd1f-b47a93062123",
      "name": "17. Sheets - Read Today's Positive Comments",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        320,
        768
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Positive Comments"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_INSTAGRAM_TRACKER_SHEET_ID"
        }
      },
      "typeVersion": 4.5,
      "alwaysOutputData": true
    },
    {
      "id": "5516c3ce-b19f-4ef7-afbb-5414b464f8a2",
      "name": "18. Code - Build Daily Digest Email",
      "type": "n8n-nodes-base.code",
      "position": [
        560,
        768
      ],
      "parameters": {
        "jsCode": "// Build the daily digest HTML email from today's saved comments\nconst allRows = $input.all();\nconst today   = new Date().toISOString().split('T')[0];\n\n// Keep only real rows saved today\n// With Always Output Data on, an empty sheet still passes one empty item through\nconst rows = allRows.filter(function(item) {\n  const d = item.json || {};\n  return d['Comment ID'] && String(d.Date || '') === today;\n});\n\nif (rows.length === 0) {\n  return [{\n    json: {\n      emailSubject: 'Instagram Positive Comments Digest - ' + today + ' - No positive comments today',\n      htmlEmail: '<p>No positive comments were found today. Keep posting great content!</p>',\n      commentCount: 0,\n      todayDate: today\n    }\n  }];\n}\n\n// Build comment cards\nconst commentCards = rows.map(function(item) {\n  const d = item.json;\n  const scoreVal = parseFloat(d['Sentiment Score'] || 0.5);\n\n  return '<div style=\"background:#f0fdf4;border-left:4px solid #22c55e;border-radius:0 8px 8px 0;padding:14px 16px;margin-bottom:12px;\">'\n    + '<div style=\"display:flex;justify-content:space-between;margin-bottom:6px;\">'\n    + '<span style=\"font-size:13px;font-weight:700;color:#166534;\">@' + (d.Username || 'unknown') + '</span>'\n    + '<span style=\"font-size:11px;color:#888;\">' + (d.Date || today) + '</span>'\n    + '</div>'\n    + '<p style=\"margin:0;font-size:14px;color:#333;line-height:1.6;\">\"' + (d['Comment Text'] || '') + '\"</p>'\n    + '<div style=\"margin-top:8px;font-size:11px;color:#666;\">'\n    + 'Score: ' + scoreVal.toFixed(2) + ' | Post: <a href=\"' + (d['Post URL'] || '#') + '\" style=\"color:#22c55e;\">View Post</a>'\n    + '</div>'\n    + '</div>';\n}).join('');\n\nconst htmlEmail =\n  '<div style=\"font-family:Arial,sans-serif;max-width:680px;margin:0 auto;background:#f5f5f5;\">'\n  + '<div style=\"background:linear-gradient(135deg,#7c3aed 0%,#db2777 100%);padding:26px 28px;border-radius:8px 8px 0 0;\">'\n  + '<h1 style=\"color:#fff;font-size:19px;margin:0;\">Instagram Positive Comments - ' + today + '</h1>'\n  + '<p style=\"color:#f3e8ff;font-size:13px;margin:6px 0 0;\">' + rows.length + ' positive comment(s) found today</p>'\n  + '</div>'\n  + '<div style=\"background:#fff;padding:22px 24px;\">'\n  + '<p style=\"font-size:14px;color:#333;line-height:1.7;margin:0 0 18px;\">Here are today\\'s positive Instagram comments. These are real customer voices showing appreciation for your content and brand.</p>'\n  + commentCards\n  + '<div style=\"background:#f3f4f6;border-radius:6px;padding:12px 16px;margin-top:16px;\">'\n  + '<p style=\"font-size:11px;color:#888;margin:0;\">Sentiment analysis by OpenAI. Only comments classified POSITIVE are saved. View full data in Google Sheets.</p>'\n  + '</div>'\n  + '</div>'\n  + '<div style=\"background:#7c3aed;padding:12px 24px;text-align:center;border-radius:0 0 8px 8px;\">'\n  + '<p style=\"color:#f3e8ff;font-size:11px;margin:0;\">Instagram Sentiment Tracker - n8n + OpenAI</p>'\n  + '</div>'\n  + '</div>';\n\nreturn [{\n  json: {\n    emailSubject: 'Instagram Digest - ' + rows.length + ' Positive Comments - ' + today,\n    htmlEmail,\n    commentCount: rows.length,\n    todayDate: today\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "e491b627-05b2-4e64-975e-30575ae7415b",
      "name": "19. Gmail - Send Daily Digest to Team",
      "type": "n8n-nodes-base.gmail",
      "position": [
        800,
        768
      ],
      "parameters": {
        "sendTo": "YOUR_TEAM_EMAIL",
        "message": "={{ $json.htmlEmail }}",
        "options": {
          "senderName": "Instagram Sentiment Tracker",
          "appendAttribution": false
        },
        "subject": "={{ $json.emailSubject }}"
      },
      "typeVersion": 2.1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "ae2d750b-640b-4645-8745-e74ae76173d4",
  "nodeGroups": [],
  "connections": {
    "14. IF - Is New Comment?": {
      "main": [
        [
          {
            "node": "15. Sheets - Save Positive Comment",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "8. Loop - Process Each Comment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5. Loop - Process Each Post": {
      "main": [
        [],
        [
          {
            "node": "6. HTTP - Fetch Comments for This Post",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. Code - Extract Posts Array": {
      "main": [
        [
          {
            "node": "5. Loop - Process Each Post",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "8. Loop - Process Each Comment": {
      "main": [
        [
          {
            "node": "5. Loop - Process Each Post",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "9. HTTP - AI Sentiment Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "11. IF - Is Positive Sentiment?": {
      "main": [
        [
          {
            "node": "12. Sheets - Check for Duplicate Comment ID",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "8. Loop - Process Each Comment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "9. HTTP - AI Sentiment Analysis": {
      "main": [
        [
          {
            "node": "10. Code - Parse Sentiment Label",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "10. Code - Parse Sentiment Label": {
      "main": [
        [
          {
            "node": "11. IF - Is Positive Sentiment?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "7. Code - Extract Comments Array": {
      "main": [
        [
          {
            "node": "8. Loop - Process Each Comment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "13. Code - Check Duplicate Result": {
      "main": [
        [
          {
            "node": "14. IF - Is New Comment?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "15. Sheets - Save Positive Comment": {
      "main": [
        [
          {
            "node": "8. Loop - Process Each Comment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "18. Code - Build Daily Digest Email": {
      "main": [
        [
          {
            "node": "19. Gmail - Send Daily Digest to Team",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. Code - Set Config and Credentials": {
      "main": [
        [
          {
            "node": "3. HTTP - Fetch Recent Instagram Posts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. Schedule - Daily 9 AM Comment Fetch": {
      "main": [
        [
          {
            "node": "2. Code - Set Config and Credentials",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "16. Schedule - Daily 8 PM Digest Email": {
      "main": [
        [
          {
            "node": "17. Sheets - Read Today's Positive Comments",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3. HTTP - Fetch Recent Instagram Posts": {
      "main": [
        [
          {
            "node": "4. Code - Extract Posts Array",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6. HTTP - Fetch Comments for This Post": {
      "main": [
        [
          {
            "node": "7. Code - Extract Comments Array",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "12. Sheets - Check for Duplicate Comment ID": {
      "main": [
        [
          {
            "node": "13. Code - Check Duplicate Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "17. Sheets - Read Today's Positive Comments": {
      "main": [
        [
          {
            "node": "18. Code - Build Daily Digest Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow runs twice daily to fetch recent Instagram post comments via the Instagram Graph API, uses OpenAI to classify sentiment, saves new positive comments to Google Sheets, and emails an HTML digest of the day’s positive comments to your team using Gmail. Runs every day…

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

More AI & RAG workflows → · Browse all categories →

Related workflows

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

AI & RAG

This workflow runs every 6 hours to read new NPS responses from Google Sheets, uses OpenAI to draft a personalized reply, sends it via Gmail, routes respondents by NPS segment, logs promoter quotes ba

Google Sheets, HTTP Request, Gmail
AI & RAG

This workflow runs daily to pull job applicants from Google Sheets, score each candidate against role requirements using the Google Gemini API, log results back to Google Sheets, and notify a Slack ch

Google Sheets, HTTP Request, Slack +1
AI & RAG

Founder's Discovery Engine. Uses googleSheets, googleDrive, httpRequest, gmail. Scheduled trigger; 18 nodes.

Google Sheets, Google Drive, HTTP Request +1
AI & RAG

Know what your competitors are doing every morning before your first meeting. This workflow visits each competitor website daily, uses OpenAI to analyse it for strategic signals, and emails your team

HTTP Request, Gmail, Google Sheets
AI & RAG

Who's this for Finance teams, AI developers, product managers, and business owners who need to monitor and control OpenAI API costs across different models and projects. If you're using GPT-4, GPT-3.5

HTTP Request, Google Sheets, Google Drive +1