{
  "id": "rbbgcf1n8aIwsPQCyOyMm",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Fetch Threads posts and send an HTML analytics report via email",
  "tags": [],
  "nodes": [
    {
      "id": "5a30db15-c360-4034-a14b-81e384388abb",
      "name": "Split Out",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        4768,
        1760
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "data"
      },
      "typeVersion": 1
    },
    {
      "id": "85c6ba79-41ef-4b4c-b7d8-7e5694c09abd",
      "name": "Fetch Threads Posts",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        4544,
        1760
      ],
      "parameters": {
        "url": "https://graph.threads.net/v1.0/me/threads",
        "options": {},
        "sendQuery": true,
        "sendHeaders": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "fields",
              "value": "id,text,timestamp,permalink"
            },
            {
              "name": "locale",
              "value": "zh_TW"
            },
            {
              "name": "limit",
              "value": "100"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $json.Threads_Token }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "d48cd4f8-319c-4796-9ce5-0549bc41ceae",
      "name": "Fetch Post Insights",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        4992,
        1760
      ],
      "parameters": {
        "url": "=https://graph.threads.net/v1.0/{{$json[\"id\"]}}/insights",
        "options": {},
        "sendQuery": true,
        "sendHeaders": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "metric",
              "value": "views,likes,replies,reposts,quotes"
            },
            {
              "name": "locale",
              "value": "zh_TW"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $('Set Token').item.json.Threads_Token }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "2f3e2e37-ef99-417f-9624-28a28202d9d1",
      "name": "Email Analytics Report",
      "type": "n8n-nodes-base.gmail",
      "position": [
        5664,
        1760
      ],
      "parameters": {
        "sendTo": "YOUR_EMAIL_ADDRESS",
        "message": "={{ $json.html }}",
        "options": {},
        "subject": "=Threads Analytics Report (Latest 100 Posts)"
      },
      "typeVersion": 2.1
    },
    {
      "id": "8ae6c331-71a1-49d3-899f-e4bf19bc4338",
      "name": "Build HTML Report",
      "type": "n8n-nodes-base.code",
      "position": [
        5440,
        1760
      ],
      "parameters": {
        "jsCode": "if (items.length === 0) {\n  return [{ json: { hasItems: false, html: '<p>No posts found.</p>' } }];\n}\n\nconst safe = s => (s || '').toString().replace(/</g,'&lt;').replace(/>/g,'&gt;');\nconst toLocal = ts => ts ? new Date(ts).toLocaleString() : '';\n\n/** Totals **/\nconst totals = items.reduce((acc, i) => {\n  const j = i.json;\n  acc.posts += 1;\n  acc.likes += Number(j.likes || 0);\n  acc.replies += Number(j.replies || 0);\n  acc.reposts += Number(j.reposts || 0);\n  acc.quotes += Number(j.quotes || 0);\n  acc.views += Number(j.views || 0);\n  return acc;\n}, {posts:0, likes:0, replies:0, reposts:0, quotes:0, views:0});\n\n/** Table rows **/\nconst rows = items\n  .sort((a,b) => new Date(b.json.timestamp) - new Date(a.json.timestamp))\n  .map(i => {\n    const j = i.json;\n    return `\n      <tr>\n        <td>${toLocal(j.timestamp)}</td>\n        <td style=\"max-width:520px;white-space:pre-wrap\">${safe(j.text).slice(0,200)}${j.text?.length>200?'\u2026':''}</td>\n        <td style=\"text-align:right\">${Number(j.likes||0)}</td>\n        <td style=\"text-align:right\">${Number(j.replies||0)}</td>\n        <td style=\"text-align:right\">${Number(j.reposts||0)}</td>\n        <td style=\"text-align:right\">${Number(j.quotes||0)}</td>\n        <td style=\"text-align:right\">${Number(j.views||0)}</td>\n        <td><a href=\"${j.permalink}\">Link</a></td>\n      </tr>`;\n  }).join('');\n\n/** HTML **/\nconst html = `\n  <div style=\"font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif\">\n    <h2 style=\"margin:0 0 8px\">Threads Analytics Report (${items.length} posts)</h2>\n    <p style=\"margin:0 0 16px\">\n      \ud83d\udc4d ${totals.likes}&nbsp;&nbsp;\ud83d\udcac ${totals.replies}&nbsp;&nbsp;\ud83d\udd01 ${totals.reposts}&nbsp;&nbsp;\ud83d\udde8\ufe0f ${totals.quotes}&nbsp;&nbsp;\ud83d\udc41\ufe0f ${totals.views}\n    </p>\n    <table border=\"1\" cellspacing=\"0\" cellpadding=\"8\" style=\"border-collapse:collapse;width:100%;font-size:14px\">\n      <thead style=\"background:#f6f7f9\">\n        <tr>\n          <th style=\"text-align:left\">Time</th>\n          <th style=\"text-align:left\">Content</th>\n          <th>\ud83d\udc4d</th>\n          <th>\ud83d\udcac</th>\n          <th>\ud83d\udd01</th>\n          <th>\ud83d\udde8\ufe0f</th>\n          <th>\ud83d\udc41\ufe0f</th>\n          <th>Link</th>\n        </tr>\n      </thead>\n      <tbody>${rows}</tbody>\n    </table>\n  </div>\n`;\n\nreturn [{ json: { hasItems: true, html } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "440e1a86-b7e6-4494-a6c1-b904def2f0e8",
      "name": "Merge Posts and Insights",
      "type": "n8n-nodes-base.code",
      "position": [
        5216,
        1760
      ],
      "parameters": {
        "jsCode": "const posts = $items('Split Out');\n\nconst OUT = items.map((ins, i) => {\n  const post = (posts[i] || {}).json || {};\n\n  const arr = ins.json?.data || [];\n  const m = Object.fromEntries(arr.map(x => [x.name, x.values?.[0]?.value]));\n\n  const ts = post.timestamp || post.created_time || post.created_at || '';\n\n  return {\n    json: {\n      id: post.id,\n      text: (post.text || '').trim(),\n      timestamp: ts,\n      permalink: post.permalink,\n      likes: m.likes ?? 0,\n      replies: m.replies ?? 0,\n      reposts: m.reposts ?? 0,\n      quotes: m.quotes ?? 0,\n      views: m.views ?? m.impressions ?? 0\n    }\n  };\n});\n\nreturn OUT;"
      },
      "typeVersion": 2
    },
    {
      "id": "d2ef1dab-12ba-4143-9ad0-589579586c8f",
      "name": "Get Threads Token",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        4096,
        1760
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "keyName": "Platform",
              "keyValue": "Threads"
            }
          ]
        },
        "operation": "get",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_DATA_TABLE_ID",
          "cachedResultUrl": "",
          "cachedResultName": "Token_Management"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "910115ef-8311-40ae-a7cc-96e8f36805bd",
      "name": "Set Token",
      "type": "n8n-nodes-base.set",
      "position": [
        4320,
        1760
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "f49e5cf9-57be-41bb-a798-bec884720c2f",
              "name": "Threads_Token",
              "type": "string",
              "value": "={{ $json.Token }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "7435fc9d-0816-404c-87c2-43fca1141cc9",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        3872,
        1664
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "15a85b84-fb71-43fa-82c7-447c4417ea81",
      "name": "Schedule Trigger (Every 2 Days)",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        3872,
        1856
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "daysInterval": 2,
              "triggerAtHour": 23
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "8cc6eee6-b33f-4662-a17d-5ab44f2ba2de",
      "name": "Sticky Note - Main",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3600,
        1184
      ],
      "parameters": {
        "width": 680,
        "height": 512,
        "content": "## How it works\n\nThis workflow pulls up to 100 of your latest Threads posts along with their engagement metrics \u2014 views, likes, replies, reposts and quotes \u2014 directly from the Threads Graph API.\n\nFor each post, a separate insights API call retrieves engagement data. The results are merged into a unified dataset, then a Code node builds a clean HTML table sorted by publish time (newest first), with a totals summary at the top. The report is delivered as an inline HTML email via Gmail.\n\nSupports both manual execution and automatic scheduling (every 2 days by default).\n\n## Setup steps\n\n1. Get a long-lived Threads access token from Meta for Developers \u2192 Threads API \u2192 Access Tokens. Enter it in the **Get Threads Token** node (Data Table, Platform = \"Threads\").\n2. Open **Email Analytics Report**, connect your Google account, and update the recipient email address.\n3. (Optional) Adjust the interval in **Schedule Trigger** \u2014 default is every 2 days at 11 pm.\n\n_Note: The Threads API allows up to 500 requests/day. Fetching 100 posts uses approximately 100 insight API calls._"
      },
      "typeVersion": 1
    },
    {
      "id": "6037d286-0bed-4636-ab2a-a01963c12d21",
      "name": "Sticky Note - Triggers",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3680,
        2016
      ],
      "parameters": {
        "color": 7,
        "width": 356,
        "height": 136,
        "content": "## Triggers\n\nRun manually on demand or on a schedule. Default: every 2 days at 11 pm. Adjust the interval in the Schedule Trigger node."
      },
      "typeVersion": 1
    },
    {
      "id": "5b3e71b3-fdac-407e-8c04-64867971de35",
      "name": "Sticky Note - Token setup",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4064,
        1904
      ],
      "parameters": {
        "color": 7,
        "width": 188,
        "height": 264,
        "content": "## Token setup\n\nReads the Threads long-lived access token from an n8n Data Table and passes it to the downstream fetch nodes."
      },
      "typeVersion": 1
    },
    {
      "id": "713c0945-958b-4f11-9b7b-96093fb2f70a",
      "name": "Sticky Note - Fetch",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4480,
        1920
      ],
      "parameters": {
        "color": 7,
        "width": 636,
        "height": 136,
        "content": "## Fetch posts & insights\n\nCalls the Threads API to retrieve up to 100 posts (id, text, timestamp, permalink), splits them into individual items, then fetches engagement metrics for each post."
      },
      "typeVersion": 1
    },
    {
      "id": "386a6cb7-046f-4c72-9cec-00b2933918ff",
      "name": "Sticky Note - Build and send",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5200,
        1920
      ],
      "parameters": {
        "color": 7,
        "width": 622,
        "height": 168,
        "content": "## Build & send report\n\nMerges post data with insights, builds a styled HTML table, and sends it as an inline email to the address configured in the Email Analytics Report node."
      },
      "typeVersion": 1
    },
    {
      "id": "c02321e2-cddb-4aeb-8e55-837b45126f7e",
      "name": "Sticky Note - More Templates",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5824,
        1440
      ],
      "parameters": {
        "color": 7,
        "width": 412,
        "height": 262,
        "content": "## More Threads automation\n\nWant to auto-publish Threads posts from a Notion CMS on a schedule?\n\nCheck out the **Content Automation Bundle** \u2014 includes Threads Publisher, LinkedIn Publisher, Facebook Publisher, AI Content Rewriter and more.\n\n\ud83d\udc49 [jasonchuang0818.gumroad.com/l/n8n-content-automation-bundle](https://jasonchuang0818.gumroad.com/l/n8n-content-automation-bundle)"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "48cf3b59-632f-4ba1-b7be-e55713c423e9",
  "connections": {
    "Set Token": {
      "main": [
        [
          {
            "node": "Fetch Threads Posts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out": {
      "main": [
        [
          {
            "node": "Fetch Post Insights",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Get Threads Token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build HTML Report": {
      "main": [
        [
          {
            "node": "Email Analytics Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Threads Token": {
      "main": [
        [
          {
            "node": "Set Token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Post Insights": {
      "main": [
        [
          {
            "node": "Merge Posts and Insights",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Threads Posts": {
      "main": [
        [
          {
            "node": "Split Out",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Posts and Insights": {
      "main": [
        [
          {
            "node": "Build HTML Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger (Every 2 Days)": {
      "main": [
        [
          {
            "node": "Get Threads Token",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}