This workflow follows the Chainllm → Emailsend 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 →
{
"name": "Main_Sourcing_Agent",
"description": null,
"active": false,
"isArchived": true,
"nodes": [
{
"parameters": {
"table": {
"__rl": true,
"value": "Topic",
"mode": "list",
"cachedResultName": "Topic"
},
"dataMode": "defineBelow",
"valuesToSend": {
"values": [
{
"column": "name",
"value": "={{ $('Code').item.json.topic }}"
},
{
"column": "frequency",
"value": "={{ $('Waiting for Topic').item.json.body.frequency }}"
},
{
"column": "search_top_x",
"value": "={{ $('Waiting for Topic').item.json.body.searchTopX }}"
},
{
"column": "user_id",
"value": "={{ $('Waiting for Topic').item.json.body.userId }}"
},
{
"column": "last_execution",
"value": "={{ $('Waiting for Topic').item.json.body.search_datetime }}"
},
{
"column": "is_newsletter",
"value": "0"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.mySql",
"typeVersion": 2.4,
"position": [
-5776,
1536
],
"id": "fc357ef9-2047-476b-b2ae-1dcdf2e1dddc",
"name": "Inserting New Topic to Database",
"credentials": {
"mySql": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "select",
"table": {
"__rl": true,
"value": "Topic",
"mode": "list",
"cachedResultName": "Topic"
},
"limit": 1,
"where": {
"values": [
{
"column": "name",
"value": "={{ $('Code').item.json.topic }}"
},
{
"column": "user_id",
"value": "={{ $('Waiting for Topic').item.json.body.userId }}"
},
{
"column": "last_execution",
"value": "={{ $('Waiting for Topic').item.json.body.search_datetime }}"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.mySql",
"typeVersion": 2.4,
"position": [
-5568,
1536
],
"id": "a4e928dd-24f1-405f-a932-d8c8cfc2ef88",
"name": "Retrieving New Topic ID",
"credentials": {
"mySql": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Access the first item from the input\nconst webhookData = $('Waiting for Topic').first().json.body;\nconst idtopic = $input.first().json.id;\n\n// Frequency mapping function\nfunction mapFrequency(freq) {\n if (freq.toLowerCase() === \"daily\") return \"d\";\n if (freq.toLowerCase() === \"weekly\") return \"w\";\n if (freq.toLowerCase() === \"monthly\") return \"m\";\n return freq; // Return original if not matched\n}\n\n// Extracting values\nconst user_id = webhookData.user_id;\nconst topic = $('Code').first().json.topic;\nconst topicDescription = $('Code').first().json.definition; // \u2190 new\nconst email = webhookData.email;\nconst top_k = webhookData.top_k;\nconst frequency = mapFrequency(webhookData.frequency);\nconst searchDatetime = webhookData.search_datetime;\n\n// Return these values so they can be used by subsequent nodes\nreturn [\n {\n json: {\n user_id,\n topic,\n topicDescription, // \u2190 included here\n email,\n top_k,\n frequency,\n searchDatetime,\n idtopic,\n },\n },\n];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-5296,
1536
],
"id": "eb1e8a88-8b0d-4a87-b21b-c3d6ed004a25",
"name": "Preparing New Input variables"
},
{
"parameters": {
"fieldsToAggregate": {
"fieldToAggregate": [
{
"fieldToAggregate": "topic"
},
{
"fieldToAggregate": "email"
},
{
"fieldToAggregate": "frequency"
},
{
"fieldToAggregate": "searchDatetime"
},
{
"fieldToAggregate": "idtopic"
},
{
"fieldToAggregate": "topicDescription"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.aggregate",
"typeVersion": 1,
"position": [
-5120,
1424
],
"id": "a621e949-42ae-49a4-8940-75b25c15560f",
"name": "Aggregating Input Variables"
},
{
"parameters": {
"httpMethod": "POST",
"path": "77b4d30b-cf0b-4297-8206-be86d0d4d52e",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
-7632,
1392
],
"id": "b66bd678-cf6a-46eb-a83b-a6ce92391a6e",
"name": "Waiting for Topic"
},
{
"parameters": {
"operation": "select",
"table": {
"__rl": true,
"value": "Topic",
"mode": "list",
"cachedResultName": "Topic"
},
"limit": 1,
"where": {
"values": [
{
"column": "user_id",
"value": "={{ $('Waiting for Topic').item.json.body.userId }}"
},
{
"column": "name",
"value": "={{ $('Code').item.json.topic }}"
}
]
},
"options": {
"detailedOutput": true
}
},
"type": "n8n-nodes-base.mySql",
"typeVersion": 2.4,
"position": [
-6160,
1392
],
"id": "ebf9f552-c5c0-418a-a16f-533c112f6e9d",
"name": "Check Existence of the Topic",
"credentials": {
"mySql": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "cc270b1f-4867-4312-87ed-677d9e35aa5e",
"leftValue": "={{ $json.data }}",
"rightValue": "",
"operator": {
"type": "array",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-5952,
1392
],
"id": "4d389ee6-5129-43f3-81be-946c4080c8cd",
"name": "Check existence of the Received Topic"
},
{
"parameters": {
"jsCode": "// Access the first item from the input\nconst webhookData = $('Waiting for Topic').first().json.body;\nconst idtopic = $input.first().json.data[0].id;\n\n// Frequency mapping function\nfunction mapFrequency(freq) {\n if (freq.toLowerCase() === \"daily\") return \"d\";\n if (freq.toLowerCase() === \"weekly\") return \"w\";\n if (freq.toLowerCase() === \"monthly\") return \"m\";\n return freq; // Return original if not matched\n}\n\n// Extracting values\nconst user_id = webhookData.user_id;\nconst topic = $('Code').first().json.topic;\nconst topicDescription = $('Code').first().json.definition; // \u2190 new\nconst email = webhookData.email;\nconst top_k = webhookData.top_k;\nconst frequency = mapFrequency(webhookData.frequency);\nconst searchDatetime = webhookData.search_datetime;\n\n// Return these values so they can be used by subsequent nodes\nreturn [\n {\n json: {\n user_id,\n topic,\n topicDescription, // \u2190 included here\n email,\n top_k,\n frequency,\n searchDatetime,\n idtopic,\n },\n },\n];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-5696,
1344
],
"id": "2dcca9c3-c6ff-4c9b-ad36-5e03651e8531",
"name": "Preparing Input Variables"
},
{
"parameters": {
"modelId": {
"__rl": true,
"value": "gpt-4o-mini",
"mode": "list",
"cachedResultName": "GPT-4O-MINI"
},
"messages": {
"values": [
{
"content": "=You are an expert in crafting highly optimized search queries for Google's SERP API.\nYour task is to generate 10 well-structured, diverse, and natural-language search queries to locate suppliers or vendors offering solutions powered by AI or generative AI related to the user\u2019s specified topic or industry.\n\n\ud83d\udd39 Instructions & Best Practices:\nUser\u2019s Topic: The user will provide a topic/industry (e.g., healthcare, marketing, supply chain, etc.).\nAI Focus: The queries must revolve around finding suppliers offering AI-driven or generative AI solutions in that topic area.\nNatural Language: Ensure the queries read like typical Google searches (no advanced operators like site:, intitle:, inurl:, etc.).\nDiversity: Provide queries that explore different angles\u2014such as solution providers, case studies, pricing, reviews, implementation strategies, or industry trends.\nConcise & Precise: Keep each query short yet descriptive for optimal search results.\n\u2705 Example\nUser Goal: \u201cFind generative AI solutions for marketing campaigns\u201d\n\nOptimized Queries:\n\n\"\"Top suppliers offering generative AI solutions for marketing,Best AI-driven platforms to automate marketing campaigns,How to use generative AI for personalized marketing content,Case studies of AI-powered marketing successes,Pricing and reviews of generative AI marketing tools,Companies specializing in AI-driven email marketing automation,Leading vendors of AI-based social media ad optimization,How generative AI is transforming digital marketing strategies,Enterprise-level AI marketing solutions for large brands,Success stories of generative AI in content creation\"\"\nMust : plz the results between only \"\" not \"\"\"\n\ud83d\udd39 Now Generate 10 Structured Search Queries\nrule : plz you response must be between only 2 quotes not 3\n",
"role": "system"
},
{
"content": "=User Goal: {{ $json.topic }} \nGoal Description : {{ $json.topicDescription }}\nQueries:\n(Provide 10 queries below, following the guidelines above. Each should naturally include mentions of \u201cAI\u201d or \u201cgenerative AI,\u201d and focus on finding suppliers or vendors in the given domain.)\nthe answer must respect this format : \"\"Query1,Query2,Query3,..,QueryN\"\"\nplz only \"\" not \"\"\""
}
]
},
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.openAi",
"typeVersion": 1.8,
"position": [
-4960,
1536
],
"id": "dcbc4538-f310-49ae-95f6-41691e959496",
"name": "Generating Queries",
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"command": "=python3 /home/node/.n8n/scripts/searcher_link.py '{{ $json.message.content }}'"
},
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
-3248,
1424
],
"id": "4d3275e2-43e4-422a-99f7-0d2222ac050e",
"name": "Searching Links"
},
{
"parameters": {
"jsCode": "// Replace \"stdout\" with your actual field name containing the JSON array of { link, title } objects\nconst jsonString = $input.first().json.stdout;\nif (!jsonString) {\n return [];\n}\n\nlet parsedData;\ntry {\n parsedData = JSON.parse(jsonString);\n} catch (error) {\n // If it's not valid JSON or fails to parse, return an empty array\n return [];\n}\n\n// Ensure parsedData is an array before we iterate\nif (!Array.isArray(parsedData)) {\n return [];\n}\n\n// Create a new array of items, each with a \"url\" and \"title\"\nreturn parsedData.map(item => {\n return {\n json: {\n url: item.link || \"\",\n title: item.title || \"\"\n }\n };\n});\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-2976,
1424
],
"id": "51b6d81c-e95c-4a76-b6ee-5c95081f32a0",
"name": "Preparing Links to Looping"
},
{
"parameters": {
"options": {}
},
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [
-2512,
1072
],
"id": "fed35b0e-68a4-4c9d-93cf-aa3b490f8511",
"name": "Loop Over Links"
},
{
"parameters": {
"operation": "select",
"table": {
"__rl": true,
"value": "Ranking",
"mode": "list",
"cachedResultName": "Ranking"
},
"returnAll": true,
"where": {
"values": [
{
"column": "Link",
"value": "={{ $json.url }}"
},
{
"column": "topic_id",
"value": "={{ $('Aggregating Input Variables').item.json.idtopic[0] }}"
}
]
},
"options": {
"detailedOutput": true
}
},
"type": "n8n-nodes-base.mySql",
"typeVersion": 2.4,
"position": [
-2288,
1136
],
"id": "7f27484a-6948-4051-80dd-6620951fe51b",
"name": "Check Existence of the Link",
"credentials": {
"mySql": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "64d548f1-5961-464b-bd52-f0550e0b7858",
"leftValue": "={{ $json.data }}",
"rightValue": "",
"operator": {
"type": "array",
"operation": "empty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-2112,
1136
],
"id": "7ed993ea-2a71-4a47-8294-ae2f5898c392",
"name": "If Link Does Not Exist, Continue"
},
{
"parameters": {
"command": "=python3 /home/node/.n8n/scripts/scraptio.py \"{{ $('Loop Over Links').item.json.url }}\""
},
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
-1616,
1056
],
"id": "b91dcfd9-205b-4556-a741-6e0a50dfc7a2",
"name": "Scrape the Link",
"retryOnFail": true,
"waitBetweenTries": 5000,
"maxTries": 5
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "b2998da2-bff5-439e-b114-a655f8245886",
"leftValue": "={{ $json.stdout }}",
"rightValue": "{\"message\": \"Internal server error\"}",
"operator": {
"type": "string",
"operation": "contains"
}
},
{
"id": "001d554a-4042-4864-a6b2-ddf49c7ab1a9",
"leftValue": "={{ $json.stdout }}",
"rightValue": "{\"error\": \"URL parameter is missing\"}",
"operator": {
"type": "string",
"operation": "contains"
}
}
],
"combinator": "or"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-1440,
1024
],
"id": "44dfa5b4-53bf-40fa-967e-dd0f1ae3dcc2",
"name": "If Scraping is Done, Continue"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "0a11d788-a57b-41ce-a59f-78138f7ea090",
"leftValue": "={{ $json.stdout }}",
"rightValue": "\"error\": \"403 Client Error",
"operator": {
"type": "string",
"operation": "contains"
}
},
{
"id": "9de670e0-2447-4317-aca6-661786d727f6",
"leftValue": "={{ $json.stdout }}",
"rightValue": "\"error\": \"404 Client Error",
"operator": {
"type": "string",
"operation": "contains"
}
},
{
"id": "6ae46cca-97f7-4953-8f19-da4dad09b800",
"leftValue": "={{ $json.stdout }}",
"rightValue": "\"error\"",
"operator": {
"type": "string",
"operation": "contains"
}
}
],
"combinator": "or"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-1632,
1376
],
"id": "1c4833a8-40ef-4612-b10b-752e8f341a3b",
"name": "If Scraping Does Not Contain Errors, Continue"
},
{
"parameters": {
"promptType": "define",
"text": "=Extract every supplier or company mentioned in the text {{ $('Message a model').item.json.message.content }} and return them in the following JSON format:\n{\n \"mentioned_suppliers\": \"Supplier1, Supplier2, ...\"\n}\nIf multiple suppliers are mentioned, separate them with commas.\n\nDo not add or remove any suppliers beyond those mentioned in the text.\n\n"
},
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"typeVersion": 1.5,
"position": [
-240,
1648
],
"id": "2811ce4f-6a13-429e-9baa-f7f70ae21215",
"name": "Format Validator Agent",
"retryOnFail": true,
"waitBetweenTries": 5000,
"maxTries": 5,
"onError": "continueErrorOutput"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "223aa338-f7c9-49fc-9516-d947439c8720",
"leftValue": "={{ $json.message.content.mentioned_suppliers }}",
"rightValue": "=suppliers_mentioned",
"operator": {
"type": "string",
"operation": "empty",
"singleValue": true
}
},
{
"id": "870f2e3f-fb4c-4c1e-a0cb-cc35ce811ad3",
"leftValue": "={{ $json.message.content.mentioned_suppliers }}",
"rightValue": "No Mentioned Suppliers",
"operator": {
"type": "string",
"operation": "contains"
}
}
],
"combinator": "or"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-320,
1056
],
"id": "a99c2828-5a35-40cd-a1f1-61b74f26569c",
"name": "If Suppliers Exist, Continue"
},
{
"parameters": {
"jsCode": "const inputText = $('If Suppliers Exist, Continue').first().json.message.content;\nconsole.log(\"debug alaeddine : \", inputText);\n\n// No need to parse - inputText is already an object\nif (!inputText.mentioned_suppliers) {\n return [];\n}\n\n// Split by comma, trim whitespace, and remove empty strings\nconst suppliersArray = inputText.mentioned_suppliers\n .split(',')\n .map(supplier => supplier.trim())\n .filter(supplier => supplier !== \"\");\n\n// Format each supplier into an output item for n8n\nreturn suppliersArray.map(supplier => ({ json: { supplier } }));"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-48,
912
],
"id": "0f5448b2-0fc1-4e33-a0aa-f785b1d86664",
"name": "Preparing Suppliers as Items",
"alwaysOutputData": true,
"onError": "continueErrorOutput"
},
{
"parameters": {
"workflowId": {
"__rl": true,
"value": "VJyAfY3h4IFN2vqV",
"mode": "list",
"cachedResultName": "LinkedIn_validator"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"Company Name": "={{ $json.supplier }}",
"Topic": "={{ $('Aggregating Input Variables').item.json.topic[0] }}",
"Topic ID": "={{ $('Aggregating Input Variables').item.json.idtopic[0] }}"
},
"matchingColumns": [
"Company Name"
],
"schema": [
{
"id": "Company Name",
"displayName": "Company Name",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true
},
{
"id": "Topic",
"displayName": "Topic",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "Topic ID",
"displayName": "Topic ID",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true,
"type": "number",
"removed": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": true
},
"mode": "each",
"options": {
"waitForSubWorkflow": true
}
},
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.2,
"position": [
112,
1056
],
"id": "ba15ea1f-c7a0-45e2-b3dc-ff3843061054",
"name": "Supplier Validator Sub Workflow",
"retryOnFail": true,
"maxTries": 5,
"waitBetweenTries": 5000
},
{
"parameters": {
"fieldsToAggregate": {
"fieldToAggregate": [
{
"fieldToAggregate": "item",
"renameField": true,
"outputFieldName": "Identified Suppliers"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.aggregate",
"typeVersion": 1,
"position": [
336,
1056
],
"id": "720e917d-18b0-46b9-8d97-7239bb1becef",
"name": "Aggregating Validated Suppliers"
},
{
"parameters": {
"jsCode": "return items.map(item => {\n // 1. R\u00e9cup\u00e9rer la liste des fournisseurs identifi\u00e9s et compter leur nombre\n const identifiedSuppliers = item.json[\"Identified Suppliers\"] || [];\n const numberOfSuppliers = identifiedSuppliers.length;\n\n // 2. R\u00e9cup\u00e9rer le lien de l\u2019\u00e9l\u00e9ment courant\n const link = $('Loop Over Links').first().json.url || \"\";\n\n // Fonction de normalisation : on retire les espaces et tous les caract\u00e8res non alphanum\u00e9riques\n const normalize = str =>\n str.toLowerCase().replace(/\\s+/g, '').replace(/[^a-z0-9]/g, '');\n\n // Normaliser le lien\n const normalizedLink = normalize(link);\n\n // 3. V\u00e9rifier si le lien contient une correspondance partielle du nom du fournisseur\n const isInHouse = identifiedSuppliers.some(supplier => {\n const normalizedSupplier = normalize(supplier);\n return normalizedLink.includes(normalizedSupplier);\n });\n\n // 4. D\u00e9terminer si c'est un fournisseur tiers\n const isThirdParty = !isInHouse;\n\n // 5. Extraire le domaine original du lien avec une expression r\u00e9guli\u00e8re\n const match = link.match(/^https?:\\/\\/([^/]+)/);\n const source = match ? match[1] : \"Invalid URL\";\n\n // 6. Retourner le r\u00e9sultat final\n return {\n json: {\n numberOfSuppliers,\n thirdParty: isThirdParty ? \"Yes\" : \"No\",\n source\n }\n };\n});\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
560,
1056
],
"id": "81919708-b27b-4c0f-a29b-424f87ed6a82",
"name": "Preparing Variables for Ranking"
},
{
"parameters": {
"jsCode": "// Retrieve the necessary variables from the first input item\nconst numberOfSuppliers = $input.first().json.numberOfSuppliers;\nconst thirdParty = $input.first().json.thirdParty; // Expected to be \"Yes\" or \"No\"\n\nlet rank;\n\n// Apply the ranking criteria:\nif (numberOfSuppliers < 1) {\n rank = 3;\n} else if (numberOfSuppliers === 1) {\n rank = 4;\n} else if (numberOfSuppliers > 1 && thirdParty === \"No\") {\n rank = 6;\n} else if (numberOfSuppliers > 1 && thirdParty === \"Yes\") {\n rank = 7;\n}\n\n// Return the result as an output item.\nreturn [\n {\n json: {\n rank: rank,\n // you can also forward other properties if needed\n numberOfSuppliers: numberOfSuppliers,\n thirdParty: thirdParty\n }\n }\n];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
768,
1056
],
"id": "eb8cb4c0-68d3-400f-ac89-488c58dc8b24",
"name": "Ranking"
},
{
"parameters": {
"table": {
"__rl": true,
"value": "Ranking",
"mode": "list",
"cachedResultName": "Ranking"
},
"dataMode": "defineBelow",
"valuesToSend": {
"values": [
{
"column": "topic_id",
"value": "={{ $('Aggregating Input Variables').item.json.idtopic[0] }}"
},
{
"column": "Identified_Suppliers",
"value": "=( {{ $('Aggregating Validated Suppliers').item.json['Identified Suppliers'] }} )"
},
{
"column": "Link",
"value": "={{ $('Loop Over Links').item.json.url }}"
},
{
"column": "Relevance_Rating",
"value": "={{ $json.rank }}"
},
{
"column": "Source",
"value": "={{ $('Preparing Variables for Ranking').item.json.source }}"
},
{
"column": "Third_Party",
"value": "={{ $('Preparing Variables for Ranking').item.json.thirdParty }}"
},
{
"column": "Article_Title",
"value": "={{ $('Loop Over Links').item.json.title }}"
},
{
"column": "Rating_Justification",
"value": "NOT NOW"
},
{
"column": "Search_Date",
"value": "={{ $('Aggregating Input Variables').item.json.searchDatetime[0] }}"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.mySql",
"typeVersion": 2.4,
"position": [
1008,
1072
],
"id": "ebdfcb06-9cf8-4eb7-a0d3-c3879f16857b",
"name": "Inserting Articles to Ranking Table",
"credentials": {
"mySql": {
"name": "<your credential>"
}
}
},
{
"parameters": {},
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [
960,
1264
],
"id": "4a963443-869a-46a3-b674-242d34069063",
"name": "If Error, Break"
},
{
"parameters": {
"operation": "select",
"table": {
"__rl": true,
"value": "Ranking",
"mode": "list",
"cachedResultName": "Ranking"
},
"returnAll": true,
"where": {
"values": [
{
"column": "Relevance_Rating",
"condition": ">=",
"value": "4"
},
{
"column": "topic_id",
"value": "={{ $('Aggregating Input Variables').item.json.idtopic[0] }}"
},
{
"column": "Search_Date",
"value": "={{ $('Aggregating Input Variables').item.json.searchDatetime[0] }}"
}
]
},
"options": {
"detailedOutput": true
}
},
"type": "n8n-nodes-base.mySql",
"typeVersion": 2.4,
"position": [
-1600,
624
],
"id": "c4600115-4a06-4c58-a53c-27f174c56e5a",
"name": "Retrieving Articles with Rank 4",
"executeOnce": true,
"credentials": {
"mySql": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// n8n Function Node: Process supplier data using nested input data\n// It assumes the input JSON has a structure like { data: [ { ... }, { ... }, ... ] }\n\n// Extract the input data array\nconst inputData = $input.first().json.data; \n\n// Extract topic from the first item (or any item since they should all have the same topic_id)\nconst topic = inputData.length > 0 ? (inputData[0].topic_id || \"\") : \"\";\n\nconst supplierData = {};\n\nfor (const item of inputData) {\n // 1. Grab the raw string, e.g. \"( Loopio, OtherCo )\"\n const identifiedRaw = item[\"Identified_Suppliers\"] || \"\";\n // 2. Strip parentheses\n const identifiedClean = identifiedRaw\n .replace(/^\\s*\\(\\s*/, \"\")\n .replace(/\\s*\\)\\s*$/, \"\");\n \n // 3. Split, trim, lowercase, filter\n const suppliers = identifiedClean\n .split(\",\")\n .map(s => s.trim().toLowerCase())\n .filter(s => s !== \"\");\n \n const link = item[\"Link\"] || \"\";\n const relevanceRating = Number(item[\"Relevance_Rating\"]) || 0;\n \n for (const supplier of suppliers) {\n if (!supplierData[supplier]) {\n supplierData[supplier] = {\n links: new Set(),\n total_relevance: 0,\n topic: topic\n };\n }\n if (link) {\n supplierData[supplier].links.add(link);\n }\n supplierData[supplier].total_relevance += relevanceRating;\n }\n}\n\n// Build and sort output\nconst outputData = Object.entries(supplierData).map(([supplier, info]) => ({\n \"Identified Suppliers\": supplier,\n \"Mentioned Links\": Array.from(info.links).join(\"; \"),\n \"Final Score\": info.total_relevance,\n \"Topic\": info.topic\n})).sort((a, b) => b[\"Final Score\"] - a[\"Final Score\"]);\n\n// Return for n8n\nreturn outputData.map(data => ({ json: data }));"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-1376,
624
],
"id": "dabd3b73-7c25-4a4e-b78e-18e1d732206b",
"name": "Calculating Final Score and Appending Mentioned Links"
},
{
"parameters": {
"options": {}
},
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [
-1056,
432
],
"id": "a05fd8e7-9bec-4bda-9d9c-b34782a61e1f",
"name": "Loop Over Suppliers"
},
{
"parameters": {
"operation": "select",
"table": {
"__rl": true,
"value": "Analysis",
"mode": "list",
"cachedResultName": "Analysis"
},
"limit": 1,
"where": {
"values": [
{
"column": "Supplier",
"value": "={{ $json[\"Identified Suppliers\"] }}"
},
{
"column": "topic_id",
"value": "={{ $json.Topic }}"
}
]
},
"options": {
"detailedOutput": true
}
},
"type": "n8n-nodes-base.mySql",
"typeVersion": 2.4,
"position": [
-848,
512
],
"id": "5ceb22c3-c9f0-47a5-ada9-225febab6da5",
"name": "Selecting Supplier ID",
"credentials": {
"mySql": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "f18416b2-2c0c-4ec2-9ad1-483d4c2dbb4a",
"leftValue": "={{ $json.data }}",
"rightValue": "",
"operator": {
"type": "array",
"operation": "empty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-656,
512
],
"id": "3a997512-dd92-46a5-9136-b81bde1881e4",
"name": "If Exists, Continue"
},
{
"parameters": {},
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [
-256,
736
],
"id": "07ce682b-b72b-4ce1-b270-512b639fa8de",
"name": "If Error, Break1"
},
{
"parameters": {
"jsCode": "// n8n Function Node: Sum scores and merge links\n\n// 1. Get the old and new final scores\nconst oldScore = Number($input.first().json.data[0][\"Final Score\"] || 0);\nconst newScore = Number($('Loop Over Suppliers').first().json[\"Final Score\"] || 0);\nconst updatedFinalScore = oldScore + newScore;\n\n// 2. Get the old and new link strings\nconst oldLinksStr = $input.first().json.data[0].Links || \"\";\nconst newLinksStr = $('Loop Over Suppliers').first().json[\"Mentioned Links\"] || \"\";\n\n// 3. Split on semicolons, trim, filter empty, and dedupe\nconst combined = [\n ...oldLinksStr.split(/;\\s*/),\n ...newLinksStr.split(/;\\s*/)\n]\n .map(link => link.trim())\n .filter(link => link !== \"\");\n\nconst updatedLinks = Array.from(new Set(combined)).join(\"; \");\n\n// 4. Return the updated values\nreturn [\n {\n json: {\n \"Updated Final Score\": updatedFinalScore,\n \"Updated Links\": updatedLinks\n }\n }\n];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-432,
512
],
"id": "38a28808-cf8d-49cd-9ea8-e3bc05676528",
"name": "Calculating New Score"
},
{
"parameters": {
"operation": "update",
"table": {
"__rl": true,
"value": "Analysis",
"mode": "list",
"cachedResultName": "Analysis"
},
"dataMode": "defineBelow",
"columnToMatchOn": "Supplier",
"valueToMatchOn": "={{ $('Loop Over Suppliers').item.json[\"Identified Suppliers\"] }}",
"valuesToSend": {
"values": [
{
"column": "Final Score",
"value": "={{ $json[\"Updated Final Score\"] }}"
},
{
"column": "Links",
"value": "={{ $json[\"Updated Links\"] }}"
},
{
"column": "Update_Date",
"value": "={{ $('Aggregating Input Variables').item.json.searchDatetime[0] }}"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.mySql",
"typeVersion": 2.4,
"position": [
-256,
512
],
"id": "fbda5c18-a398-4462-9a81-48e2d5daffd2",
"name": "Updating Final Score",
"credentials": {
"mySql": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"mode": "chooseBranch",
"useDataOfInput": 2
},
"type": "n8n-nodes-base.merge",
"typeVersion": 3,
"position": [
-768,
96
],
"id": "887374ea-c563-48e2-88c8-c1710f71b8a9",
"name": "Merge Outputs"
},
{
"parameters": {
"sendTo": "={{ $('Merge Outputs').item.json.email[0] }}",
"subject": "=\u201c {{ $('Merge Outputs').item.json.topic[0] }} \u201d Analysis Finished \u2013 View Results",
"message": "=<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <title>Topic Processing Complete</title>\n </head>\n <body style=\"margin:0; padding:0; font-family: Arial, sans-serif; background-color:#f4f4f4;\">\n <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\">\n <tr>\n <td align=\"center\" style=\"padding: 20px 0;\">\n <table width=\"600\" cellpadding=\"0\" cellspacing=\"0\" style=\"background-color:#ffffff; border-radius:8px; overflow:hidden; box-shadow:0 2px 4px rgba(0,0,0,0.1);\">\n \n <!-- Header -->\n <tr>\n <td style=\"background-color:#0044cc; padding:20px; text-align:center;\">\n <h1 style=\"color:#ffffff; margin:0; font-size:24px;\">Processing Complete</h1>\n </td>\n </tr>\n \n <!-- Body -->\n <tr>\n <td style=\"padding:30px; color:#333333; line-height:1.6;\">\n <p style=\"margin-top:0;\">Hello,</p>\n <p>We\u2019re pleased to let you know that the processing for the topic <strong> {{ $('Calculating Runpod Consumption').item.json.topic[0] }} </strong> has been finalized.</p>\n <p style=\"text-align:center; margin:30px 0;\">\n <a href=\"http://beta-agent.apaia-technology.io/sourcing-agent/#/login\" \n style=\"background-color:#007bff; color:#ffffff; text-decoration:none; \n padding:12px 24px; border-radius:4px; display:inline-block; font-size:16px;\">\n View Your Results\n </a>\n </p>\n <p>If you have any questions or need further clarification, simply reply to this email\u2014we\u2019re here to help!</p>\n <p style=\"margin-bottom:0;\">Best regards,<br/>\n <strong>APAIA Technology</strong><br/>\n\n </p>\n </td>\n </tr>\n \n <!-- Footer -->\n <tr>\n <td style=\"background-color:#f0f0f0; padding:15px; text-align:center; font-size:12px; color:#777777;\">\n \u00a9 2025 APAIA Technology \u2014 All rights reserved.\n </td>\n </tr>\n \n </table>\n </td>\n </tr>\n </table>\n </body>\n</html>\n",
"options": {}
},
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
208,
-192
],
"id": "81044c16-0921-4fd4-b718-808b4d536323",
"name": "Send Email When Workflow Successfully Executed"
},
{
"parameters": {
"command": "=runpodctl create pod \\\n --name \"{{ $('Waiting for Topic').item.json.body.userId }}_{{ $json.topic }}\" \\\n --gpuType \"NVIDIA A100-SXM4-80GB\" \\\n --imageName ollama/ollama \\\n --containerDiskSize 40 \\\n --networkVolumeId u31ph7h1dc \\\n --volumePath /root/.ollama \\\n --ports 11434/http \\\n --secureCloud\n"
},
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
-6832,
1392
],
"id": "a3188cb5-637a-475c-a3f3-e47befac4495",
"name": "Create a pod",
"disabled": true
},
{
"parameters": {
"jsCode": "// n8n Function Node (JavaScript)\n// Each incoming item must have item.json.stdout containing:\n// pod \"<ID>\" created for $X / hr\n// Works with many incoming items.\n\nreturn $input.all().map(item => {\n const text = (item.json.stdout ?? \"\").toString();\n\n // Capture the ID inside quotes after the word \u201cpod\u201d\n const m = text.match(/pod\\s+\"([^\"]+)\"/i);\n if (!m) {\n throw new Error(`Pod ID not found in: ${text}`);\n }\n\n const podId = m[1];\n const link = `https://${podId}-11434.proxy.runpod.net`;\n\n // Return the item with the new fields added\n return {\n json: {\n ...item.json,\n pod_id: podId,\n pod_link: link\n }\n };\n});\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-6608,
1392
],
"id": "34e2fa22-4dca-4bfe-83e5-2c1c90e909e7",
"name": "Retrieve Ollama Address & Pod ID",
"disabled": true
},
{
"parameters": {
"amount": 120
},
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1,
"position": [
-6432,
1392
],
"id": "c027f9ec-a851-4db3-afa8-a5aadef6167e",
"name": "Wait 2 minutes",
"disabled": true
},
{
"parameters": {
"content": "## Pod Creation\n",
"height": 300,
"width": 620,
"color": 6
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-7088,
1328
],
"id": "f439b853-86a9-4ac7-8a87-bbeae0e6eb1b",
"name": "Sticky Note"
},
{
"parameters": {
"content": "## Checking Topic Unicity\n",
"height": 400,
"width": 1300
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-6432,
1296
],
"id": "9a83f34c-796f-49df-9ad4-02db2d75c5b9",
"name": "Sticky Note1"
},
{
"parameters": {
"content": "## Queries Generator",
"height": 280,
"width": 320,
"color": 2
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-4992,
1376
],
"id": "b2edd03f-64fe-4b9d-b0f3-a3bad551ffe4",
"name": "Sticky Note2"
},
{
"parameters": {
"content": "## Link Google Searcher",
"height": 280,
"width": 680,
"color": 2
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-4512,
1376
],
"id": "9f7b1e24-aa89-487d-a934-be2cb68ee989",
"name": "Sticky Note3"
},
{
"parameters": {
"content": "## Link Processing",
"height": 620,
"width": 3640,
"color": 3
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-3040,
976
],
"id": "9c65542e-5015-4234-8b44-b3ff39e45735",
"name": "Sticky Note4"
},
{
"parameters": {
"content": "## Link Unicity Check",
"height": 240,
"width": 420
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-3456,
1072
],
"id": "d474e204-8c86-4f18-bc43-53ee659ffb5a",
"name": "Sticky Note5"
},
{
"parameters": {
"content": "## Link Scraper\n",
"height": 240,
"width": 560
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-2960,
992
],
"id": "7e3479b6-9c28-4745-9b3a-3ebfd4d634f0",
"name": "Sticky Note6"
},
{
"parameters": {
"content": "## Supplier Extractor & Validator",
"height": 360,
"width": 900
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-2368,
992
],
"id": "808376e1-09f3-4424-83c4-379a689c6299",
"name": "Sticky Note7"
},
{
"parameters": {
"content": "## Supplier Validator Via Linkedin Sub Workflow",
"height": 360,
"width": 600
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-1312,
1104
],
"id": "27acd29a-035d-4af8-8ab8-304d1f7f7982",
"name": "Sticky Note8"
},
{
"parameters": {
"content": "## Article Ranking",
"height": 360,
"width": 380
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-688,
1104
],
"id": "d9297432-e4df-4f33-bcf1-1aaf355386b1",
"name": "Sticky Note9"
},
{
"parameters": {
"content": "## Inserting Article Into Database",
"height": 260,
"width": 280
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-416,
992
],
"id": "6d3317c1-1829-44be-820b-032b4d35d8aa",
"name": "Sticky Note10"
},
{
"parameters": {
"content": "## Calculating Suppliers' Final Score\n",
"height": 520,
"width": 1820,
"color": 5
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-2272,
432
],
"id": "ff660d56-30da-4fd4-b5f7-5c844c91c976",
"name": "Sticky Note11"
},
{
"parameters": {
"jsCode": "const COST_PER_HR = 1.89;\n\nfunction parseLocal(str) { // <-- changed\n return new Date(str.replace(' ', 'T')); // no Z suffix\n}\n\nreturn $input.all().map(item => {\n const tsStr = typeof item.json.searchDatetime === 'string'\n ? item.json.searchDatetime\n : (item.json.searchDatetime ?? [])[0];\n\n if (!tsStr) throw new Error('searchDatetime missing');\n\n const start = parseLocal(tsStr);\n if (isNaN(start)) throw new Error(`Bad date: ${tsStr}`);\n\n const now = new Date();\n const hours = (now - start) / 3_600_000; // ms \u2192 h (should be \u2265 0)\n const total = hours * COST_PER_HR;\n\n return {\n json: {\n ...item.json,\n hoursElapsed: Number(hours.toFixed(2)),\n totalCost: Number(total.toFixed(2))\n }\n };\n});\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-400,
112
],
"id": "c20c8108-c85c-403c-954a-6c96714cf796",
"name": "Calculating Runpod Consumption",
"disabled": true
},
{
"parameters": {
"table": {
"__rl": true,
"value": "Runpod_Consumption",
"mode": "list",
"cachedResultName": "Runpod_Consumption"
},
"dataMode": "defineBelow",
"valuesToSend": {
"values": [
{
"column": "user_id",
"value": "={{ $('Waiting for Topic').item.json.body.userId }}"
},
{
"column": "user_email",
"value": "={{ $('Waiting for Topic').item.json.body.email }}"
},
{
"column": "topic_id",
"value": "={{ $json.idtopic[0] }}"
},
{
"column": "topic_name",
"value": "={{ $json.topic }}"
},
{
"column": "pod_id",
"value": "={{ $('Retrieve Ollama Address & Pod ID').item.json.pod_id }}"
},
{
"column": "pod_price",
"value": "1.89"
},
{
"column": "execution_started",
"value": "={{ $('Waiting for Topic').item.json.body.search_datetime }}"
},
{
"column": "execution_time",
"value": "={{ $json.hoursElapsed }}"
},
{
"column": "cost",
"value": "={{ $json.totalCost }}"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.mySql",
"typeVersion": 2.4,
"position": [
-192,
112
],
"id": "563bbd9e-8303-44b7-8312-8d30956ae02a",
"name": "Insert Runpod Consumption Details",
"disabled": true
},
{
"parameters": {
"command": "=runpodctl remove pod {{ $('Retrieve Ollama Address & Pod ID').item.json.pod_id }}"
},
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
-16,
112
],
"id": "546e926a-86c0-4238-ade5-ce89692c3710",
"name": "Delete Running Pod",
"disabled": true
},
{
"parameters": {
"content": "## Calculating Runpod Consumption Details\n## Deleting Running Pod\n",
"height": 300,
"width": 660,
"color": 7
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-1792,
16
],
"id": "89d48424-b860-4e67-82be-9bd2c415addd",
"name": "Sticky Note12"
},
{
"parameters": {
"operation": "executeQuery",
"query": "INSERT INTO use_case_topics (\n user_id, email,\n use_case_input, expected_output, goal,\n chosen_topic, environment, -- leave environment NULL for now\n ranked_topics, topic_description, topic_vendors_examples,\n search_datetime\n) VALUES (\n {{ $json.body.userId }},\n '{{ $json.body.email }}',\n\n -- escape any single quotes in the free\u2011text fields\n '{{ $json.body.useCaseInput.replaceAll(\"'\", \"\\\\'\") }}',\n '{{ $json.body.expectedOutput.replaceAll(\"'\", \"\\\\'\") }}',\n '{{ $json.body.goal.replaceAll(\"'\", \"\\\\'\") }}',\n\n '{{ $json.body.chosenTopic }}',\n NULL, -- or 'Greenfield' / 'Brownfield' if you send it\n\n -- arrays \u2192 JSON columns\n CAST('{{ JSON.stringify($json.body.rankedTopics) }}' AS JSON),\n '{{ $json.body.topicDescription.replaceAll(\"'\", \"\\\\'\") }}',\n CAST('{{ JSON.stringify($json.body.companies) }}' AS JSON),\n\n -- ISO\u20118601 \u2192 MySQL DATETIME\n '{{ $json.body.search_datetime.replace(\"T\", \" \") }}'\n);\n",
"options": {}
},
"type": "n8n-nodes-base.mySql",
"typeVersion": 2.4,
"position": [
-8112,
1616
],
"id": "fb23e39f-8a71-491d-9303-1ee3992c6948",
"name": "MySQL1",
"disabled": true
},
{
"parameters": {
"operation": "select",
"table": {
"__rl": true,
"value": "Topic",
"mode": "list",
"cachedResultName": "Topic"
},
"limit": 1,
"where": {
"values": [
{
"column": "name",
"value": "={{ $('Waiting for Topic').item.json.body.topic }}"
}
]
},
"options": {
"detailedOutput": true
}
},
"type": "n8n-nodes-base.mySql",
"typeVersion": 2.4,
"position": [
-7952,
1632
],
"id": "951e79fa-da41-45ee-9882-f6b727a822cf",
"name": "MySQL",
"disabled": true
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "b484cbf9-177a-4d0f-a65f-92f3c8051849",
"leftValue": "={{ $json.data }}",
"rightValue": "",
"operator": {
"type": "array",
"operation": "empty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
-7728,
1616
],
"id": "f0c291bf-04f5-46c6-85f1-9e3b2f1c2aef",
"name": "If",
"disabled": true
},
{
"parameters": {
"table": {
"__rl": true,
"value": "Topic",
"mode": "list",
"cachedResultName": "Topic"
},
"dataMode": "defineBelow",
"valuesToSend": {
"values": [
{
"column": "frequency",
"value": "={{ $('Waiting for Topic').item.json.body.frequency }}"
},
{
"column": "last_execution",
"value": "={{ $('Waiting for Topic').item.json.body.search_datetime }}"
},
{
"column": "name",
"value": "={{ $('Waiting for Topic').item.json.body.topic }}"
},
{
"column": "search_top_x",
"value": "={{ $('Waiting for Topic').item.json.body.searchTopX }}"
},
{
"column": "user_id",
"value": "={{ $('Waiting for Topic').item.json.body.userId }}"
},
{
"column": "is_newsletter",
"value": "0"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.mySql",
"typeVersion": 2.4,
"position": [
-7600,
1584
],
"id": "4040e0d9-9cce-4d05-9926-fcc38033083d",
"name": "MySQL2",
"disabled": true
},
{
"parameters": {
"operation": "select",
"table": {
"__rl": true,
"value": "Topic",
"mode": "list",
"cachedResultName": "Topic"
},
"limit": 1,
"where": {
"values": [
{
"column": "name",
"value": "={{ $('Waiting for Topic').item.json.body.topic }}"
},
{
"column": "user_id",
"value": "={{ $('Waiting for Topic').item.json.body.userId }}"
},
{
"column": "last_execution",
"value": "={{ $('Waiting for Topic').item.json.body.search_datetime }}"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.mySql",
"typeVersion": 2.4,
"position": [
-7360,
1808
],
"id": "c810a3d5-a309-46ac-998d-c1a6d5d8d161",
"name": "Retrieving New Topic ID1",
"disabled": true
},
{
"parameters": {
"operation": "executeQuery",
"query": "INSERT INTO beta_agent.Ranking (\n Article_Title,\n Identified_Suppliers,\n Link,\n Rating_Justification,\n Relevance_Rating,\n Search_Date,\n Source,\n Third_Party,\n topic_id -- new topic ID\n)\nSELECT\n Article_Title,\n Identified_Suppliers,\n Link,\n Rating_Justification,\n Relevance_Rating,\n Search_Date,\n Source,\n Third_Party,\n {{ $json.id }} -- \u2190 targetTopicId (new)\nFROM beta_agent.Ranking\nWHERE topic_id = {{ $('MySQL').item.json.data[0].id }}; -- \u2190 sourceTopicId\n",
"options": {}
},
"type": "n8n-nodes-base.mySql",
"typeVersion": 2.4,
"position": [
-7168,
1856
],
"id": "cb6e0866-16a8-4a64-975c-1bd67f7802c8",
"name": "MySQL3",
"disabled": true
},
{
"parameters": {
"operation": "executeQuery",
"query": "INSERT INTO beta_agent.Analysis (\n `Company Size`,\n `Description`,\n `Field Specification`,\n `Final Score`,\n `Founded`,\n `Headquarters`,\n `Industry`,\n `LinkedIn`,\n `Links`,\n `Locations`,\n `Supplier`,\n `Update_Date`,\n `Website`,\n `topic_id`\n)\nSELECT\n `Company Size`,\n `Description`,\n `Field Specification`,\n `Final Score`,\n `Founded`,\n `Headquarters`,\n `Industry`,\n `LinkedIn`,\n `Links`,\n `Locations`,\n `Supplier`,\n `Update_Date`,\n `Website`,\n {{ $('Retrieving New Topic ID1').item.json.id }} -- new topic_id\nFROM beta_agent.Analysis\nWHERE `topic_id` = {{ $('MySQL').item.json.data[0].id }}; -- source topic_id\n",
"options": {}
},
"type": "n8n-nodes-base.mySql",
"typeVersion": 2.4,
"position": [
-6992,
1872
],
"id": "814997f1-147a-460a-842a-52a395fdc338",
"name": "MySQL4",
"disabled": true
},
{
"parameters": {
"jsCode": "// This runs for every incoming item\nreturn items.map(item => {\n const text = $input.first().json.body.topic; // replace with the actual field name\n\n // Split off the \"Example Vendors\" part\n const [mainPart, vendorsPart = ''] = text.split('|').map(s => s.trim());\n\n // Extract topic and definition\n const [topic = '', definition = ''] = mainPart.split(' - ').map(s => s.trim());\n\n // Clean up and split the vendors list\n const exampleVendors = vendorsPart\n .replace(/^Example Vendors:\\s*/i, '')\n .split(',')\n .map(v => v.trim())\n .filter(v => v);\n\n return {\n json: {\n topic,\n definition,\n exampleVendors\n }\n };\n});\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-7376,
1392
],
"id": "9e75c362-233f-4463-97aa-58273cee9e06",
"name": "Code"
},
{
"parameters": {
"fromEmail": "contact@apaia-technology.io",
"toEmail": "={{ $('Merge Outputs').item.json.email }}",
"subject": "=\u201c {{ $('Merge Outputs').item.json.topic[0] }} \u201d Analysis Finished \u2013 View Results",
"html": "=<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <title>Topic Processing Complete</title>\n </head>\n <body style=\"margin:0; padding:0; font-family: Arial, sans-serif; background-color:#f4f4f4;\">\n <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\">\n <tr>\n <td align=\"center\" style=\"padding: 20px 0;\">\n <table width=\"600\" cellpadding=\"0\" cellspacing=\"0\" style=\"background-color:#ffffff; border-radius:8px; overflow:hidden; box-shadow:0 2px 4px rgba(0,0,0,0.1);\">\n \n <!-- Header -->\n <tr>\n <td style=\"background-color:#0044cc; padding:20px; text-align:center;\">\n <h1 style=\"color:#ffffff; margin:0; font-size:24px;\">Processing Complete</h1>\n </td>\n </tr>\n \n <!-- Body -->\n <tr>\n <td style=\"padding:30px; color:#333333; line-height:1.6;\">\n <p style=\"margin-top:0;\">Hello,</p>\n <p>We\u2019re pleased to let you know that the processing for the topic <strong> {{ $('Calculating Runpod Consumption').item.json.topic[0] }} </strong> has been finalized.</p>\n <p style=\"text-align:center; margin:30px 0;\">\n <a href=\"http://beta-agent.apaia-technology.io\" \n style=\"background-color:#007bff; color:#ffffff; text-decoration:none; \n padding:12px 24px; border-radius:4px; display:inline-block; font-size:16px;\">\n View Your Results\n </a>\n </p>\n <p>If you have any questions or need further clarification, simply reply to this email\u2014we\u2019re here to help!</p>\n <p style=\"margin-bottom:0;\">Best regards,<br/>\n <strong>APAIA Technology</strong><br/>\n\n </p>\n </td>\n </tr>\n \n <!-- Footer -->\n <tr>\n <td style=\"background-color:#f0f0f0; padding:15px;
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
mySqlopenAiApismtptavilyApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Main_Sourcing_Agent. Uses mySql, openAi, executeCommand, chainLlm. Webhook trigger; 70 nodes.
Source: https://github.com/alaeddine-hash/docker-n8n-exports/blob/main/Main_Sourcing_Agent__9e41b7d8-49d5-4975-a0df-ab9e41d27af5.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
LLMS_Query_Links_stabilisation. Uses mySql, lmChatOpenAi, executeCommand, chainLlm. Webhook trigger; 95 nodes.
LLMS_Query_Links_stabilisation. Uses mySql, lmChatOpenAi, executeCommand, chainLlm. Webhook trigger; 95 nodes.
sourcing_agent_production. Uses mySql, openAi, executeCommand, emailSend. Webhook trigger; 78 nodes.
sourcing_agent_production. Uses mySql, openAi, executeCommand, emailSend. Webhook trigger; 78 nodes.
Main_Sourcing_Agent. Uses mySql, openAi, executeCommand, chainLlm. Webhook trigger; 70 nodes.