{
  "name": "Hyperion Weekly Newsletter",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                2
              ],
              "triggerAtHour": 8,
              "triggerAtMinute": 0
            }
          ]
        }
      },
      "id": "b7fc5ca8-af24-4d93-97d3-c37ea205b210",
      "name": "Every Tuesday 08:00 CET",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        340
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "https://hyperion-consulting.io/api/insights/latest",
        "options": {}
      },
      "id": "9b9a4668-3a36-4f50-a607-cc4dbbbf8a71",
      "name": "Fetch Insights",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        280,
        0
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "const item = $input.first().json;\n\n// Handle HTTP request failure or missing data\nif (!item || item.error || !item.posts) {\n  return [{ json: { _type: 'insights', posts: [] } }];\n}\n\nconst cutoff = new Date();\ncutoff.setDate(cutoff.getDate() - 14);\n\nconst posts = item.posts\n  .filter(p => new Date(p.publishedAt) >= cutoff)\n  .slice(0, 3);\n\nreturn [{ json: { _type: 'insights', posts } }];"
      },
      "id": "16cd44a8-d624-4d14-b9c3-c3ba8dc06632",
      "name": "Parse Insights",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        520,
        0
      ]
    },
    {
      "parameters": {
        "url": "https://www.artificial-intelligence-act.com/feed",
        "options": {}
      },
      "id": "a096957d-fc7b-4b45-a5eb-55f5c02e74e8",
      "name": "RSS: EU AI Act",
      "type": "n8n-nodes-base.rssFeedRead",
      "typeVersion": 1.1,
      "position": [
        280,
        200
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "url": "https://venturebeat.com/category/ai/feed",
        "options": {}
      },
      "id": "608e6425-741b-49c8-b838-fe664f82d2b4",
      "name": "RSS: VentureBeat AI",
      "type": "n8n-nodes-base.rssFeedRead",
      "typeVersion": 1.1,
      "position": [
        280,
        360
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "url": "https://www.themanufacturer.com/feed",
        "options": {}
      },
      "id": "0d03ba24-decb-4107-b3d1-567c71208c11",
      "name": "RSS: The Manufacturer",
      "type": "n8n-nodes-base.rssFeedRead",
      "typeVersion": 1.1,
      "position": [
        280,
        520
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "url": "https://electrek.co/feed",
        "options": {}
      },
      "id": "8a056b35-32bb-46a0-8063-79b2bb47c1fb",
      "name": "RSS: Electrek",
      "type": "n8n-nodes-base.rssFeedRead",
      "typeVersion": 1.1,
      "position": [
        280,
        680
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "mode": "append",
        "numberInputs": 4
      },
      "id": "b843d153-52b2-4a7d-affe-4e46b0165c3b",
      "name": "Merge RSS Feeds",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        540,
        440
      ]
    },
    {
      "parameters": {
        "jsCode": "const items = $input.all();\nconst cutoff = new Date();\ncutoff.setDate(cutoff.getDate() - 7);\n\nfunction getSource(url) {\n  if (!url) return 'Industry News';\n  if (url.includes('artificial-intelligence-act')) return 'EU AI Act';\n  if (url.includes('venturebeat')) return 'VentureBeat';\n  if (url.includes('themanufacturer')) return 'The Manufacturer';\n  if (url.includes('electrek')) return 'Electrek';\n  return 'Industry News';\n}\n\nconst news = items\n  .filter(i => i.json.title && i.json.link)\n  .filter(i => {\n    const d = new Date(i.json.isoDate || i.json.pubDate);\n    return !isNaN(d.getTime()) && d >= cutoff;\n  })\n  .map(i => ({\n    title: i.json.title,\n    url: i.json.link,\n    source: getSource(i.json.link),\n    summary: (i.json.contentSnippet || '').substring(0, 200),\n    date: i.json.isoDate || i.json.pubDate,\n  }))\n  .sort((a, b) => new Date(b.date) - new Date(a.date))\n  .slice(0, 4);\n\nreturn [{ json: { _type: 'news', items: news } }];"
      },
      "id": "798b2ca8-e698-4b73-907e-6fc11645ac55",
      "name": "Filter & Rank News",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        760,
        440
      ]
    },
    {
      "parameters": {
        "mode": "append",
        "numberInputs": 2
      },
      "id": "0cfc6cdd-34d7-4ca4-bb62-04544e48312d",
      "name": "Merge All Content",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        960,
        220
      ]
    },
    {
      "parameters": {
        "jsCode": "const items = $input.all();\nlet insights = [];\nlet news = [];\n\nfor (const item of items) {\n  if (item.json._type === 'insights') {\n    insights = item.json.posts || [];\n  } else if (item.json._type === 'news') {\n    news = item.json.items || [];\n  }\n}\n\nlet insightsBlock = '';\nif (insights.length > 0) {\n  insightsBlock = 'OUR LATEST INSIGHTS:\\n' + insights.map(p =>\n    '- Title: ' + p.title + '\\n  Excerpt: ' + p.excerpt + '\\n  URL: ' + p.url + '\\n  Date: ' + p.publishedAt\n  ).join('\\n');\n} else {\n  insightsBlock = 'No recent insights articles this week (skip the \"This Week at Hyperion\" section).';\n}\n\nlet newsBlock = '';\nif (news.length > 0) {\n  newsBlock = 'INDUSTRY NEWS:\\n' + news.map(n =>\n    '- Title: ' + n.title + '\\n  Source: ' + n.source + '\\n  Summary: ' + n.summary + '\\n  URL: ' + n.url\n  ).join('\\n');\n} else {\n  newsBlock = 'No recent industry news found (use general AI/industry trends instead).';\n}\n\nconst systemPrompt = `You are the editorial voice of Hyperion Consulting, an AI consulting firm for European industrial SMEs. The founder is Mohammed Cherifi \\u2014 AI Ambassador for the French Government, Forbes Technology Council member, and builder of AuraLinkOS (a 319-microservice EV charging platform built solo in 2 months).\n\nTone: authoritative but approachable. Sharp, no fluff. Problem-first. Never generic. Always tie insights back to what industrial SMEs can act on.\nBrand positioning: \"I don't stop at PowerPoint. I ship.\"`;\n\nconst userPrompt = `Generate a weekly newsletter for Hyperion Consulting's audience of European industrial SME leaders.\n\n${insightsBlock}\n\n${newsBlock}\n\nStructure the newsletter as follows:\n1. **Subject line** \\u2014 punchy, 8 words max. Output on its own line prefixed with \"SUBJECT: \"\n2. **Opening hook** \\u2014 2\\u20133 sentences, a provocative insight or stat\n3. **This Week at Hyperion** \\u2014 highlight 1\\u20132 of our Insights articles with a 2-sentence teaser + link (skip if no insights provided)\n4. **What's Moving the Industry** \\u2014 3\\u20134 news items, each with a 1-sentence take from Mohammed's POV\n5. **One Thing to Do This Week** \\u2014 a single, concrete action for an industrial SME leader\n6. **Closing line** \\u2014 brief, human, no corporate speak\n\nOutput the newsletter as clean, responsive HTML suitable for email. Use inline CSS only. Use a clean sans-serif font stack. Keep the design minimal and professional \\u2014 white background, dark text, blue (#2563eb) accent color for links and headings.\n\nInclude an unsubscribe placeholder at the bottom: {{{ unsubscribe_url }}}\n\nIMPORTANT: The very first line of your response must be: SUBJECT: <your subject line>\nThen output the HTML starting from the second line.`;\n\nreturn [{\n  json: {\n    systemPrompt,\n    userPrompt,\n    hasInsights: insights.length > 0,\n    hasNews: news.length > 0\n  }\n}];"
      },
      "id": "e87f2ccd-b233-4956-a144-cca29972a0d4",
      "name": "Build Newsletter Prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1180,
        220
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.mistral.ai/v1/chat/completions",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "={{ 'Bearer ' + $vars.MISTRAL_API_KEY }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ model: 'mistral-large-latest', messages: [{ role: 'system', content: $json.systemPrompt }, { role: 'user', content: $json.userPrompt }], max_tokens: 4000, temperature: 0.7 }) }}",
        "options": {
          "timeout": 60000
        }
      },
      "id": "c7f2da81-8775-4228-8585-38d1d4fdbc4d",
      "name": "Generate Newsletter (Mistral)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1400,
        220
      ]
    },
    {
      "parameters": {
        "jsCode": "const response = $input.first().json;\nconst content = response.choices[0].message.content;\nconst lines = content.split('\\n');\nlet subject = 'Hyperion Weekly: AI Insights for Industrial Leaders';\nlet htmlStart = 0;\n\nfor (let i = 0; i < lines.length; i++) {\n  const line = lines[i].trim();\n  if (line.startsWith('SUBJECT:')) {\n    subject = line.replace('SUBJECT:', '').trim();\n    htmlStart = i + 1;\n    break;\n  }\n}\n\nlet html = lines.slice(htmlStart).join('\\n').trim();\n\n// Extract from code block if wrapped\nconst codeBlockRegex = /```html?\\s*\\n?([\\s\\S]*?)```/;\nconst match = html.match(codeBlockRegex);\nif (match) {\n  html = match[1].trim();\n}\n\nreturn [{\n  json: {\n    subject,\n    html,\n    generatedAt: new Date().toISOString()\n  }\n}];"
      },
      "id": "d8813eef-1708-4f7e-b858-2d69f636649f",
      "name": "Extract Subject & HTML",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1620,
        220
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.resend.com/broadcasts",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "={{ 'Bearer ' + $vars.RESEND_API_KEY }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ audience_id: $vars.RESEND_NEWSLETTER_AUDIENCE_ID, from: 'Mohammed Cherifi <newsletter@hyperion-consulting.io>', reply_to: ['mcherifi@outlook.com'], subject: $json.subject, html: $json.html, name: 'Weekly Newsletter - ' + new Date().toISOString().split('T')[0] }) }}",
        "options": {}
      },
      "id": "ff317c14-126f-4fc6-a234-53de7777bd1f",
      "name": "Create Broadcast (Resend)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1840,
        220
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ 'https://api.resend.com/broadcasts/' + $json.id + '/send' }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "={{ 'Bearer ' + $vars.RESEND_API_KEY }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "{}",
        "options": {}
      },
      "id": "82351a53-f46c-4f10-9a40-a39a5ef1c777",
      "name": "Send Broadcast",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2060,
        220
      ]
    },
    {
      "parameters": {
        "jsCode": "const result = $input.first().json;\nconst log = {\n  timestamp: new Date().toISOString(),\n  broadcastId: result.id || 'unknown',\n  status: 'broadcast_sent',\n  workflow: 'Hyperion Weekly Newsletter'\n};\nconsole.log('Newsletter broadcast sent:', JSON.stringify(log));\nreturn [{ json: log }];"
      },
      "id": "9bd32d1d-c18e-4c9c-a4ab-3df9dde71f38",
      "name": "Log Result",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2280,
        220
      ]
    },
    {
      "parameters": {
        "content": "## ⚙️ Setup Instructions\n\n1. Go to **Settings > Variables** and add:\n   - `MISTRAL_API_KEY`\n   - `RESEND_API_KEY`\n   - `RESEND_NEWSLETTER_AUDIENCE_ID`\n\n2. Deploy `/api/insights/latest` endpoint\n\n3. Click **Test workflow** to run manually\n\n4. Toggle **Active** to enable the schedule",
        "height": 310,
        "width": 340,
        "color": 4
      },
      "id": "19f01b20-b243-4341-9fec-8e45e6f46eaa",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -340,
        140
      ]
    }
  ],
  "connections": {
    "Every Tuesday 08:00 CET": {
      "main": [
        [
          {
            "node": "Fetch Insights",
            "type": "main",
            "index": 0
          },
          {
            "node": "RSS: EU AI Act",
            "type": "main",
            "index": 0
          },
          {
            "node": "RSS: VentureBeat AI",
            "type": "main",
            "index": 0
          },
          {
            "node": "RSS: The Manufacturer",
            "type": "main",
            "index": 0
          },
          {
            "node": "RSS: Electrek",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Insights": {
      "main": [
        [
          {
            "node": "Parse Insights",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Insights": {
      "main": [
        [
          {
            "node": "Merge All Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "RSS: EU AI Act": {
      "main": [
        [
          {
            "node": "Merge RSS Feeds",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "RSS: VentureBeat AI": {
      "main": [
        [
          {
            "node": "Merge RSS Feeds",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "RSS: The Manufacturer": {
      "main": [
        [
          {
            "node": "Merge RSS Feeds",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "RSS: Electrek": {
      "main": [
        [
          {
            "node": "Merge RSS Feeds",
            "type": "main",
            "index": 3
          }
        ]
      ]
    },
    "Merge RSS Feeds": {
      "main": [
        [
          {
            "node": "Filter & Rank News",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter & Rank News": {
      "main": [
        [
          {
            "node": "Merge All Content",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge All Content": {
      "main": [
        [
          {
            "node": "Build Newsletter Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Newsletter Prompt": {
      "main": [
        [
          {
            "node": "Generate Newsletter (Mistral)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Newsletter (Mistral)": {
      "main": [
        [
          {
            "node": "Extract Subject & HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Subject & HTML": {
      "main": [
        [
          {
            "node": "Create Broadcast (Resend)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Broadcast (Resend)": {
      "main": [
        [
          {
            "node": "Send Broadcast",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Broadcast": {
      "main": [
        [
          {
            "node": "Log Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "timezone": "Europe/Berlin"
  },
  "tags": [],
  "meta": {
    "templateCredsSetupCompleted": true
  }
}