{
  "name": "News monitor (share copy)",
  "nodes": [
    {
      "parameters": {
        "url": "={{ $json.feed }}\n",
        "options": {}
      },
      "type": "n8n-nodes-base.rssFeedRead",
      "typeVersion": 1.1,
      "position": [
        120,
        -1060
      ],
      "id": "08a6e8ea-21be-424e-b269-ab15133e66e1",
      "name": "RSS Read"
    },
    {
      "parameters": {
        "jsCode": "function simpleHash(str) {\n  let hash = 0;\n  if (str.length === 0) return hash.toString();\n  for (let i = 0; i < str.length; i++) {\n    const char = str.charCodeAt(i);\n    hash = ((hash << 5) - hash) + char;\n    hash |= 0;\n  }\n  return Math.abs(hash).toString();\n}\n\nfunction extractDomain(link) {\n  if (!link || typeof link !== 'string') return 'Unknown';\n  const match = link.match(/^https?:\\/\\/([^\\/?#]+)(?:[\\/?#]|$)/i);\n  return match ? match[1].replace(/^www\\./, '') : 'Unknown';\n}\n\n\nreturn items.map(item => {\n  const data = item.json;\n\n  // --- URL (first)\n  const rawLink = data.link || '';\n  const url = typeof rawLink === 'string' ? rawLink : '';\n\n  // --- Title\n  const title = data.title?.['#text'] || data.title || '';\n\n  // --- Content\n  let content = '';\n  if (typeof data.content === 'object') {\n    content = data.content['#text'] || '';\n  } else if (typeof data.content === 'string') {\n    content = data.content;\n  }\n\n  if (!content && data['content:encoded']) content = data['content:encoded'];\n  if (!content && data.description) content = data.description;\n  if (!content && data.summary) content = data.summary;\n\n  // --- Creator (fallback to domain)\n  const creator =\n    data['author']?.name ||\n    data['itunes:author'] ||\n    data['dc:creator'] ||\n    data['author'] ||\n    extractDomain(url);\n\n  // --- pubDate\n  const pubDate =\n    data.pubDate ||\n    data.published ||\n    data.updated ||\n    data['dc:date'] ||\n    data.date ||\n    null;\n\n  // --- GUID\n  const rawGuid = data.guid?.trim() || data.id?.trim() || '';\n  const fallbackGuid = simpleHash(`${title}${url}${pubDate}`);\n  const guid = rawGuid || fallbackGuid;\n\n  return {\n    json: {\n      title,\n      content,\n      url,\n      creator,\n      pubDate,\n      guid,\n    }\n  };\n});\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        500,
        -1060
      ],
      "id": "73013b86-408b-40d8-b186-883528767ee5",
      "name": "Normalize feed content"
    },
    {
      "parameters": {
        "content": "## News ingestion and evalutation\nFor feeds where not everything is of interest.\nRuns a couple of times per day.",
        "height": 840,
        "width": 2360,
        "color": 3
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -520,
        -1420
      ],
      "typeVersion": 1,
      "id": "dff9e169-cddf-461a-8bd5-589b05b1b5fb",
      "name": "Sticky Note3"
    },
    {
      "parameters": {
        "operation": "removeItemsSeenInPreviousExecutions",
        "dedupeValue": "={{ $json.link }}",
        "options": {}
      },
      "type": "n8n-nodes-base.removeDuplicates",
      "typeVersion": 2,
      "position": [
        300,
        -1060
      ],
      "id": "19a90286-cddb-4c42-801d-0ae05723f98a",
      "name": "Remove Duplicates"
    },
    {
      "parameters": {
        "modelId": {
          "__rl": true,
          "value": "gpt-4o-mini",
          "mode": "list",
          "cachedResultName": "GPT-4O-MINI"
        },
        "messages": {
          "values": [
            {
              "content": "=You are an expert content curator for {{PERSON}}, who works at {{ORGANIZATION}} and has a strong interest in {{INTERESTS}}. Your task is to evaluate whether a news item aligns with {{PERSON}}'s specific areas of focus.\n\n## ABOUT {{PERSON}}\n- Works at {{ORGANIZATION}}, which focuses on {{ORG_FOCUS}}  \n- Follows writers and blogs such as {{EXAMPLE_WRITERS_OR_SOURCES}}  \n\n## {{PERSON}}'s CORE INTERESTS\n- {{INTEREST_1}}  \n- {{INTEREST_2}}  \n- {{INTEREST_3}}, including:  \n  - {{SUBTOPIC_A}}  \n  - {{SUBTOPIC_B}}  \n  - {{SUBTOPIC_C}}  \n\n## EVALUATION CRITERIA\nRate this news item on a scale of 1–5 based on:\n- Relevance to {{PERSON}}'s professional focus  \n- Alignment with {{PERSON}}'s broader thematic interests  \n- Potential to offer unique insights or perspectives relevant to {{PERSON}}’s context  \n\n## NEWS ITEM TO EVALUATE  \nTitle: {{ $json.title }}  \nContent: {{ $json.content }}  \n\n## OUTPUT FORMAT  \nProvide your evaluation as valid JSON with the following structure:\n{\n  \"score\": [1-5 integer],\n  \"reasoning\": \"[2–3 sentences explaining your evaluation. Do not refer to {{PERSON}} by name or mention that this is a summary for them — just explain *why* the content is insightful or not.]\"\n}\n\nFocus on substance over hype, and prioritize content with meaningful implications, critical insights, or depth. Avoid overvaluing purely technical updates or surface-level news without broader significance.\n"
            }
          ]
        },
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 1.8,
      "position": [
        700,
        -1060
      ],
      "id": "3610a2c8-129a-4ae5-82b0-a117054e8552",
      "name": "Evaluate news",
      "credentials": {
        "openAiApi": {
          "id": "xxxx",
          "name": "OpenAi account"
        }
      }
    },
    {
      "parameters": {
        "content": "## Newsletter ingestion\nFor feeds where everything is of interest.\nRuns a couple of times per day.",
        "height": 640,
        "width": 2340,
        "color": 3
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -500,
        -480
      ],
      "typeVersion": 1,
      "id": "3905570f-2c77-4699-9602-b58b296ec342",
      "name": "Sticky Note4"
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 0,4,8,12,16,20 * * *"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -300,
        -1060
      ],
      "id": "441e81ee-3485-489c-ba8b-fd349ce419da",
      "name": "Fetch aggregator feeds"
    },
    {
      "parameters": {
        "tableId": "newsMonitor_filteredItems",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "headline",
              "fieldValue": "={{ $('Normalize feed content').item.json.title }}"
            },
            {
              "fieldId": "url",
              "fieldValue": "={{ $('Normalize feed content').item.json.url }}"
            },
            {
              "fieldId": "content",
              "fieldValue": "={{ $('Normalize feed content').item.json.content }}"
            },
            {
              "fieldId": "ranking",
              "fieldValue": "={{ $json.evaluationScore }}"
            },
            {
              "fieldId": "evalReasoning",
              "fieldValue": "={{ $json.evaluationReasoning }}"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        1480,
        -1060
      ],
      "id": "0074f507-9f79-470a-b246-e23b321b1e34",
      "name": "Store news items",
      "credentials": {
        "supabaseApi": {
          "id": "xxx",
          "name": "Supabase account"
        }
      }
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 0 4,9,12,18,23 * * * "
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -300,
        -200
      ],
      "id": "9adae138-7901-433b-bb60-2d0623521ff9",
      "name": "Fetch newsletters"
    },
    {
      "parameters": {
        "url": "={{ $json.feed }}\n",
        "options": {
          "ignoreSSL": false
        }
      },
      "type": "n8n-nodes-base.rssFeedRead",
      "typeVersion": 1.1,
      "position": [
        400,
        -200
      ],
      "id": "4e71277f-e5fb-48fa-8424-6c742234d751",
      "name": "Parse newsletter",
      "retryOnFail": false,
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "be7b5860-911b-42f9-9916-2ab629661b91",
              "leftValue": "={{ $json }}",
              "rightValue": "",
              "operator": {
                "type": "object",
                "operation": "empty",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        60,
        540
      ],
      "id": "416a8a4f-34e1-421c-b83e-e1f9434883f9",
      "name": "Check if there are new newsletters"
    },
    {
      "parameters": {
        "jsCode": "// This function extracts and combines all the summaries from the OpenAI responses\nconst summaries = items.map(item => {\n  // Extract the content from each message\n  return item.json.message.content;\n});\n\n// Combine all summaries into one text with separators\nconst combinedText = summaries.join(\"\\n\\n\");\n\n// Return a single item with the combined text\nreturn [{\n  json: {\n    combinedSummaries: combinedText\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        660,
        600
      ],
      "id": "a0f53ffc-9648-49e7-8ce6-eaaa72396f54",
      "name": "Combine newsletter summaries"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "4797c549-4dab-4677-b21a-fc9457a5d379",
              "name": "newsletters",
              "value": "={{ $json.combinedSummaries }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        820,
        600
      ],
      "id": "8fd1c6c7-ae3a-453a-964a-a79396211b9b",
      "name": "Merge prep newsletters"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "430c54f9-7c1f-4438-9087-5753dbe928c9",
              "name": "top",
              "value": "={{ $json.message.content }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        820,
        780
      ],
      "id": "9dc50e79-78fc-4247-add9-055ccffed0c9",
      "name": "Merge prep top news"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "a8cac0a6-d070-430d-affe-704b7b35e00a",
              "name": "misc",
              "value": "={{ $json.message.content }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        820,
        1000
      ],
      "id": "92f12ad9-8a3e-4eb3-afd3-2bfe2ff54506",
      "name": "Merge prep misc"
    },
    {
      "parameters": {
        "jsCode": "// Access the content from each Edit Fields node\nconst firstContent = $input.item.json.newsletters || \"No content available\";\nconst secondContent = $input.item.json.top || \"No content available\";\nconst thirdContent = $input.item.json.misc || \"No content available\";\n\n// Combine them into a single markdown message\nconst combinedMarkdown = `\n${firstContent}\n\n# Top news\n${secondContent}\n\n# Misc news\n${thirdContent}\n`;\n\n// Return the combined content\nreturn {\n  json: {\n    combinedMarkdown\n  }\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1280,
        780
      ],
      "id": "71c73086-0607-4c5a-8308-e66079726dd9",
      "name": "Compile news summary"
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 7 * * *"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -340,
        780
      ],
      "id": "e6ec69c1-e31d-4d88-811c-59c098f2a060",
      "name": "Schedule Trigger"
    },
    {
      "parameters": {
        "content": "## Compile daily newsletters\nRuns once per day. Adjust when in the trigger.",
        "height": 1180,
        "width": 2360,
        "color": 3
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -500,
        280
      ],
      "typeVersion": 1,
      "id": "baf398a7-c124-4e79-add7-5e7e9722b023",
      "name": "Sticky Note1"
    },
    {
      "parameters": {
        "fromEmail": "<sender>",
        "toEmail": "<recipient>",
        "subject": "News roundup",
        "html": "={{ $json.content }}",
        "options": {}
      },
      "type": "n8n-nodes-base.emailSend",
      "typeVersion": 2.1,
      "position": [
        1660,
        780
      ],
      "id": "b9e5b6e2-86e4-4ab5-9ee9-201005fb4d16",
      "name": "Send Email",
      "webhookId": "e0ef077e-e160-472c-985f-4e87c82b75d2",
      "credentials": {
        "smtp": {
          "id": "xxx",
          "name": "SMTP account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "function simpleHash(str) {\n  let hash = 0;\n  if (str.length === 0) return hash.toString();\n  for (let i = 0; i < str.length; i++) {\n    const char = str.charCodeAt(i);\n    hash = ((hash << 5) - hash) + char;\n    hash |= 0;\n  }\n  return Math.abs(hash).toString();\n}\n\nfunction extractDomain(link) {\n  if (!link || typeof link !== 'string') return 'Unknown';\n  const match = link.match(/^https?:\\/\\/([^\\/?#]+)(?:[\\/?#]|$)/i);\n  return match ? match[1].replace(/^www\\./, '') : 'Unknown';\n}\n\n\nreturn items.map(item => {\n  const data = item.json;\n\n  // --- URL (first)\n  const rawLink = data.link || '';\n  const url = typeof rawLink === 'string' ? rawLink : '';\n\n  // --- Title\n  const title = data.title?.['#text'] || data.title || '';\n\n  // --- Content\n  let content = '';\n  if (typeof data.content === 'object') {\n    content = data.content['#text'] || '';\n  } else if (typeof data.content === 'string') {\n    content = data.content;\n  }\n\n  if (!content && data['content:encoded']) content = data['content:encoded'];\n  if (!content && data.description) content = data.description;\n  if (!content && data.summary) content = data.summary;\n\n  // --- Creator (fallback to domain)\n  const creator =\n    data['author']?.name ||\n    data['itunes:author'] ||\n    data['dc:creator'] ||\n    data['author'] ||\n    extractDomain(url);\n\n  // --- pubDate\n  const pubDate =\n    data.pubDate ||\n    data.published ||\n    data.updated ||\n    data['dc:date'] ||\n    data.date ||\n    null;\n\n  // --- GUID\n  const rawGuid = data.guid?.trim() || data.id?.trim() || '';\n  const fallbackGuid = simpleHash(`${title}${url}${pubDate}`);\n  const guid = rawGuid || fallbackGuid;\n\n  return {\n    json: {\n      title,\n      content,\n      url,\n      creator,\n      pubDate,\n      guid,\n    }\n  };\n});\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        600,
        -200
      ],
      "id": "a0842b09-f698-4427-9475-d0ee763073bf",
      "name": "Normalize feed items"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://<your-supabase-id>.supabase.co/rest/v1/newsMonitor_newsletters?on_conflict=guid|",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "supabaseApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Prefer",
              "value": "resolution=merge-duplicates,return=minimal"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($items().map(item => ({\n  guid: item.json.guid,\n  title: item.json.title,\n  url: item.json.url,\n  content: item.json.content,\n  creator: item.json.creator,\n  pubDate: item.json.pubDate\n}))) }}\n",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        800,
        -200
      ],
      "id": "043452d5-e5b1-4e9b-97c4-53cfd86ed3e6",
      "name": "Upsert Supabase",
      "credentials": {
        "supabaseApi": {
          "id": "xxx",
          "name": "Supabase account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const feeds = [\n  \"https://www.feed.com/feed\"\n];\n\nreturn feeds.map(feed => ({ json: { feed } }));"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -100,
        -200
      ],
      "id": "682cb5bf-e846-4ac9-9784-86d4abfe43c1",
      "name": "Newsletter list"
    },
    {
      "parameters": {
        "options": {
          "reset": false
        }
      },
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 3,
      "position": [
        160,
        -200
      ],
      "id": "24161ac0-9a03-40ce-ac04-defaeba4d449",
      "name": "Loop over newsletters"
    },
    {
      "parameters": {
        "jsCode": "const feeds = [\n\"https://hnrss.org/newest?points=300\",\n\"https://www.techmeme.com/feed.xml\"\n];\n\nreturn feeds.map(feed => ({ json: { feed } }));"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -80,
        -1060
      ],
      "id": "06e07cea-7f09-4f01-af46-4f3e08fc6299",
      "name": "Feed list"
    },
    {
      "parameters": {
        "jsCode": "// This function processes each item in the input array\nconst processedItems = $input.all().map(item => {\n  const originalItem = item.json;  // Your original news item\n  \n  try {\n    // Get the LLM response content (which is a JSON string)\n    const llmResponseString = originalItem.message.content;\n    \n    // Parse the JSON string into an object\n    const evaluation = JSON.parse(llmResponseString);\n    \n    // Return the original item with added evaluation data\n    return {\n      evaluationScore: evaluation.score,\n      evaluationReasoning: evaluation.reasoning,\n    };\n  } catch (error) {\n    // Handle any parsing errors\n    console.log(\"Error processing item:\", error);\n    return {\n      evaluationError: error.message,\n      evaluationScore: 0,\n      evaluationReasoning: \"Error processing evaluation\",\n      include: false\n    };\n  }\n});\n\n// Return the processed items\nreturn processedItems;"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1040,
        -1060
      ],
      "id": "73c15f0f-a840-4d59-aa51-8c78dff4a281",
      "name": "Add structure to LLM response"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "69d6bf8a-b41f-473d-89a5-de5195c08a6a",
              "leftValue": "={{ $json.evaluationScore }}",
              "rightValue": 2,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1240,
        -1060
      ],
      "id": "a19f80cc-8dd5-410b-955d-713f9c5ef77c",
      "name": "Select only top news"
    },
    {
      "parameters": {
        "operation": "getAll",
        "tableId": "newsMonitor_filteredItems",
        "returnAll": true,
        "matchType": "allFilters",
        "filters": {
          "conditions": [
            {
              "keyName": "ranking",
              "condition": "gte",
              "keyValue": "4"
            },
            {
              "keyName": "created_at",
              "condition": "gte",
              "keyValue": "={{ new Date(new Date().setDate(new Date().getDate() - 1))     .toISOString()     .slice(0, 10) + 'T04:00:00.000000+00:00' }}"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        -120,
        780
      ],
      "id": "1f1b4eeb-9d89-49e4-8ab1-79fea46a095d",
      "name": "Fetch top ranked news",
      "credentials": {
        "supabaseApi": {
          "id": "xxx",
          "name": "Supabase account"
        }
      }
    },
    {
      "parameters": {
        "operation": "getAll",
        "tableId": "newsMonitor_filteredItems",
        "returnAll": true,
        "matchType": "allFilters",
        "filters": {
          "conditions": [
            {
              "keyName": "ranking",
              "condition": "eq",
              "keyValue": "3"
            },
            {
              "keyName": "created_at",
              "condition": "gte",
              "keyValue": "={{ new Date(new Date().setDate(new Date().getDate() - 1))     .toISOString()     .slice(0, 10) + 'T04:00:00.000000+00:00' }}"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        -100,
        1000
      ],
      "id": "833aaa0c-b696-47f3-91c4-de0d6a5291bf",
      "name": "Fetch misc  news",
      "credentials": {
        "supabaseApi": {
          "id": "xxx",
          "name": "Supabase account"
        }
      }
    },
    {
      "parameters": {
        "operation": "getAll",
        "tableId": "newsMonitor_newsletters",
        "returnAll": true,
        "matchType": "allFilters",
        "filters": {
          "conditions": [
            {
              "keyName": "pubDate",
              "condition": "gte",
              "keyValue": "={{ new Date(new Date().setDate(new Date().getDate() - 1))     .toISOString()     .slice(0, 10) + 'T04:00:00.000000+00:00' }}"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.supabase",
      "typeVersion": 1,
      "position": [
        -120,
        540
      ],
      "id": "d1b2269c-b8ee-4955-9328-20f5a7d4acc4",
      "name": "Fetch newsletters1",
      "alwaysOutputData": true,
      "credentials": {
        "supabaseApi": {
          "id": "xxx",
          "name": "Supabase account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "return [{\n    formattedText: items.map((item, index) => \n      `${index + 1}. Title: ${item.json.headline}\\n   Content: ${item.json.content}`\n    ).join(\"\\n\\n\")\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        80,
        780
      ],
      "id": "4dab43e9-ffa6-4172-b684-0bd3b77aafb1",
      "name": "Merge top news"
    },
    {
      "parameters": {
        "jsCode": "return [{\n    formattedText: items.map((item, index) => \n      `${index + 1}. Title: ${item.json.headline}\\n   Content: ${item.json.content}`\n    ).join(\"\\n\\n\")\n}];\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        100,
        1000
      ],
      "id": "551d3b94-9412-4790-9a6b-1e137fd8b0b0",
      "name": "Merge misc news"
    },
    {
      "parameters": {
        "modelId": {
          "__rl": true,
          "value": "gpt-4o-mini",
          "mode": "list",
          "cachedResultName": "GPT-4O-MINI"
        },
        "messages": {
          "values": [
            {
              "content": "=You are an expert newsletter summarizer for {{PERSON}}, who works at {{ORG}} and has a deep interest in {{INTERESTS}}. Your task is to efficiently summarize newsletters to help {{PERSON}} decide what deserves immediate attention and what can wait.\n\nPROCESS  \nWhen presented with a newsletter, analyze its content and structure.\n\nWrite 2-3 sentences that concisely summarize why this newsletter is of interest to {{PERSON}} and their colleagues.\n\nABOUT {{PERSON}}  \n- Works at {{ORG}}, a center focused on {{ORG_MISSION}}  \n- Follows writers and blogs such as {{WRITERS}}  \n\n{{PERSON}}'s CORE INTERESTS  \n- {{INTEREST_AREA_1}}  \n- {{INTEREST_AREA_2}} (e.g. the intersection of technology and society):  \n  - {{SUBTOPIC_1}}  \n  - {{SUBTOPIC_2}}  \n  - {{SUBTOPIC_3}}  \n  - {{SUBTOPIC_4}}  \n- {{OTHER_RELEVANT_AREAS}}  \n\n## NEWSLETTER ARTICLE TO SUMMARIZE  \nTitle: {{ $json.title }}  \nContent: {{ $json.content }}  \n\n## OUTPUT FORMAT (MAKE SURE TO USE HTML AND INCLUDE THE CREATOR!)  \n<h1><a href=\"{{ $json.url }}\">{{ $json.title }}</a></h1>  \nBy {{ $json.creator }}.  \n2-3 sentences that concisely summarize why this newsletter is relevant to {{PERSON}}’s interests.\n\n## DON'TS  \n- DO NOT directly mention {{PERSON}} or any recipient by name in the summary  \n- DO NOT mention any specific organization by name  \n- DO NOT begin with phrases like \"In this newsletter...\" – go straight to the core insights  \n"
            }
          ]
        },
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 1.8,
      "position": [
        280,
        600
      ],
      "id": "53fb3d8e-524c-487e-96e1-e97a778642b3",
      "name": "Newsletter summaries",
      "credentials": {
        "openAiApi": {
          "id": "xxx",
          "name": "OpenAi account"
        }
      }
    },
    {
      "parameters": {
        "modelId": {
          "__rl": true,
          "value": "gpt-4o-mini",
          "mode": "list",
          "cachedResultName": "GPT-4O-MINI"
        },
        "messages": {
          "values": [
            {
              "content": "=You are an expert news curator and analyst. Your task is to organize a collection of news articles into meaningful categories that would help readers quickly understand the major stories of the day.\n\nThe input data contains news articles with URLs, titles, and importance rankings (1-5, with 5 being most important/relevant):\n\n{{ $json.formattedText }}\n\nPlease:\n1. Group articles into logical categories based on shared themes, topics, or narratives\n2. Sort the categories with the most significant news stories first\n3. Within each category, list articles in order of importance (highest ranking first)\n4. Ensure each category has a clear, descriptive title that captures the theme\n5. Aim for 4-8 categories total, depending on the diversity of news topics present. Ensure no article appears in more than one category.\n\n\nRespond in the following format:\n\n<h2>[Category Title 1]</h2>\n<ul>\n  <li><a href=\"[article_url]\">[Article Title]</a></li>\n  <li><a href=\"[article_url]\">[Article Title]</a></li>\n</ul>\n\n<h2>[Category Title 2]</h2>\n<ul>\n  <li><a href=\"[article_url]\">[Article Title]</a></li>\n  <li><a href=\"[article_url]\">[Article Title]</a></li>\n</ul>"
            }
          ]
        },
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 1.8,
      "position": [
        280,
        800
      ],
      "id": "ff53ed5a-0e2b-4033-ba4b-bf264335957a",
      "name": "Top news summaries",
      "credentials": {
        "openAiApi": {
          "id": "xxx",
          "name": "OpenAi account"
        }
      }
    },
    {
      "parameters": {
        "modelId": {
          "__rl": true,
          "value": "gpt-4o-mini",
          "mode": "list",
          "cachedResultName": "GPT-4O-MINI"
        },
        "messages": {
          "values": [
            {
              "content": "=You are an expert news curator and analyst. Your task is to organize a collection of news articles into meaningful categories that would help readers quickly understand the major stories of the day.\n\nThe input data contains news articles with URLs, titles, and importance rankings (1-5, with 5 being most important/relevant):\n\n{{ $json.formattedText }}\n\nPlease:\n1. Group articles into logical categories based on shared themes, topics, or narratives\n2. Sort the categories with the most significant news stories first\n3. Within each category, list articles in order of importance (highest ranking first)\n4. Ensure each category has a clear, descriptive title that captures the theme\n5. Aim for 4-8 categories total, depending on the diversity of news topics present. Ensure no article appears in more than one category.\n\n\nRespond in the following format:\n\n<h2>[Category Title 1]</h2>\n<ul>\n  <li><a href=\"[article_url]\">[Article Title]</a></li>\n  <li><a href=\"[article_url]\">[Article Title]</a></li>\n</ul>\n\n<h2>[Category Title 2]</h2>\n<ul>\n  <li><a href=\"[article_url]\">[Article Title]</a></li>\n  <li><a href=\"[article_url]\">[Article Title]</a></li>\n</ul>"
            }
          ]
        },
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 1.8,
      "position": [
        320,
        1020
      ],
      "id": "a1d73f10-a0e6-4924-871c-f0e3d7903eda",
      "name": "Misc news summaries",
      "credentials": {
        "openAiApi": {
          "id": "xxx",
          "name": "OpenAi account"
        }
      }
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineByPosition",
        "numberInputs": 3,
        "options": {}
      },
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        1060,
        780
      ],
      "id": "9f8d9efc-59d0-4b3e-a548-39936b963fa6",
      "name": "Merge everything"
    },
    {
      "parameters": {
        "mode": "markdownToHtml",
        "markdown": "={{ $json.combinedMarkdown }}",
        "destinationKey": "content",
        "options": {
          "simplifiedAutoLink": true
        }
      },
      "type": "n8n-nodes-base.markdown",
      "typeVersion": 1,
      "position": [
        1480,
        780
      ],
      "id": "bb780595-235c-479b-9d2a-2e229ed9ae44",
      "name": "Email prep"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "b725bbc6-8725-4527-b36f-7ad534a93de5",
              "name": "newsletters",
              "value": "No new newsletters today.",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        340,
        300
      ],
      "id": "748e867a-924e-474d-9bb0-ee3cf30ee917",
      "name": "Set newsletter content if nothing new"
    },
    {
      "parameters": {
        "content": "## Update! \nAdd the feeds you want to monitor and evaluate here.",
        "height": 340,
        "width": 260
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -160,
        -1180
      ],
      "typeVersion": 1,
      "id": "28314047-3780-4885-b656-de416935b8e0",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "content": "## Update! \nEdit the LLM prompt to match your interests. Also make sure to add your API key for OpenAI access, or change node to the provider you want to use.",
        "height": 340,
        "width": 340
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        660,
        -1200
      ],
      "typeVersion": 1,
      "id": "6c42a324-045f-4976-911b-259bd8b05842",
      "name": "Sticky Note2"
    },
    {
      "parameters": {
        "content": "## Update! \nEnter your own API credentials for Supabase. Also make sure to create the two tables needed. See blogpost.",
        "height": 340,
        "width": 300
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1400,
        -1220
      ],
      "typeVersion": 1,
      "id": "01d58da8-0ceb-4899-8b52-aa9bcb7ede8b",
      "name": "Sticky Note5"
    },
    {
      "parameters": {
        "content": "## Update! \nAdd the feeds you want to summarize.",
        "height": 340,
        "width": 260
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -180,
        -320
      ],
      "typeVersion": 1,
      "id": "36d718ac-e039-4b9f-a55b-c018309f0b9b",
      "name": "Sticky Note6"
    },
    {
      "parameters": {
        "content": "## Update! \nEdit the URL for the Supabase API endpoint.",
        "height": 340,
        "width": 260
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        740,
        -320
      ],
      "typeVersion": 1,
      "id": "240b168b-2f71-4d70-bc5e-c27e099baf4c",
      "name": "Sticky Note7"
    },
    {
      "parameters": {
        "content": "## Update! \nEdit the LLM prompts to match your needs and interests. Also make sure to add your API key for OpenAI access, or change node to the provider you want to use.",
        "height": 760,
        "width": 360
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        260,
        460
      ],
      "typeVersion": 1,
      "id": "a97f215e-d99b-4969-bfb1-91fe6f787b68",
      "name": "Sticky Note8"
    },
    {
      "parameters": {
        "content": "## Update! \nEdit email addresses, and also add credentials for a SMTP server.",
        "height": 340
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1600,
        660
      ],
      "typeVersion": 1,
      "id": "7e6ed4ff-f3d1-4b2c-b141-69aab3257403",
      "name": "Sticky Note9"
    },
    {
      "parameters": {
        "content": "# Media monitoring\nn8n workflow that monitors RSS/Atom feeds, saves links and/or entries to Supabase, and once per day sends an email with a summary of the most important news in the last day.\n\nBuilt by anders@thoresson.net. More in [this blogpost](https://anders.thoresson.se/post/2025/03/sa-anvaender-jag-n8n-och-gpt-4o-mini-foer-omvaerldsbevakning/).",
        "height": 3280,
        "width": 2860,
        "color": 6
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -780,
        -1640
      ],
      "typeVersion": 1,
      "id": "da6b86c3-00f6-490e-8474-e7b7a92deea7",
      "name": "Sticky Note10"
    },
    {
      "parameters": {
        "content": "## Update! \nIf you change the timing of this workflow, also update the time limits for these queries.",
        "height": 840,
        "width": 220
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -180,
        380
      ],
      "typeVersion": 1,
      "id": "db99e62e-1f1f-4fc7-9f68-ee62f3ca5dd3",
      "name": "Sticky Note11"
    }
  ],
  "pinData": {},
  "connections": {
    "RSS Read": {
      "main": [
        [
          {
            "node": "Remove Duplicates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize feed content": {
      "main": [
        [
          {
            "node": "Evaluate news",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Remove Duplicates": {
      "main": [
        [
          {
            "node": "Normalize feed content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Evaluate news": {
      "main": [
        [
          {
            "node": "Add structure to LLM response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch aggregator feeds": {
      "main": [
        [
          {
            "node": "Feed list",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch newsletters": {
      "main": [
        [
          {
            "node": "Newsletter list",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse newsletter": {
      "main": [
        [
          {
            "node": "Normalize feed items",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Loop over newsletters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check if there are new newsletters": {
      "main": [
        [
          {
            "node": "Set newsletter content if nothing new",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Newsletter summaries",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine newsletter summaries": {
      "main": [
        [
          {
            "node": "Merge prep newsletters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge prep newsletters": {
      "main": [
        [
          {
            "node": "Merge everything",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge prep top news": {
      "main": [
        [
          {
            "node": "Merge everything",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge prep misc": {
      "main": [
        [
          {
            "node": "Merge everything",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Compile news summary": {
      "main": [
        [
          {
            "node": "Email prep",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Fetch top ranked news",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch misc  news",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch newsletters1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize feed items": {
      "main": [
        [
          {
            "node": "Upsert Supabase",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upsert Supabase": {
      "main": [
        [
          {
            "node": "Loop over newsletters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Newsletter list": {
      "main": [
        [
          {
            "node": "Loop over newsletters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop over newsletters": {
      "main": [
        [],
        [
          {
            "node": "Parse newsletter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Feed list": {
      "main": [
        [
          {
            "node": "RSS Read",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add structure to LLM response": {
      "main": [
        [
          {
            "node": "Select only top news",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Select only top news": {
      "main": [
        [
          {
            "node": "Store news items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch top ranked news": {
      "main": [
        [
          {
            "node": "Merge top news",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch misc  news": {
      "main": [
        [
          {
            "node": "Merge misc news",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch newsletters1": {
      "main": [
        [
          {
            "node": "Check if there are new newsletters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge top news": {
      "main": [
        [
          {
            "node": "Top news summaries",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge misc news": {
      "main": [
        [
          {
            "node": "Misc news summaries",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Newsletter summaries": {
      "main": [
        [
          {
            "node": "Combine newsletter summaries",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Top news summaries": {
      "main": [
        [
          {
            "node": "Merge prep top news",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Misc news summaries": {
      "main": [
        [
          {
            "node": "Merge prep misc",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge everything": {
      "main": [
        [
          {
            "node": "Compile news summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Email prep": {
      "main": [
        [
          {
            "node": "Send Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set newsletter content if nothing new": {
      "main": [
        [
          {
            "node": "Merge everything",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "timezone": "Europe/Stockholm",
    "callerPolicy": "workflowsFromSameOwner"
  },
  "versionId": "d5b472a0-9b7c-4ff8-a3d7-2e579dae0cae",
  "meta": {
    "templateCredsSetupCompleted": true,
    "instanceId": "06065e5d4e808806899a05a133080be62cf388b20ecc7b245226037ff5a4f131"
  },
  "id": "srzX9X7aJZ6tEbqv",
  "tags": []
}