2026-03-17 09:31:03 +01:00
{
"name" : "Workflow A - Mail Processing (HTTP)" ,
"description" : "Fetch unprocessed conversations from Freescout, analyze with AI, save suggestions" ,
"nodes" : [
{
"id" : "uuid-trigger-1" ,
"name" : "Trigger" ,
"type" : "n8n-nodes-base.cron" ,
"typeVersion" : 1 ,
"position" : [ 250 , 200 ] ,
"parameters" : {
"cronExpression" : "*/5 * * * *"
}
} ,
{
"id" : "uuid-get-conversations" ,
"name" : "Get Unprocessed Conversations" ,
"type" : "n8n-nodes-base.httpRequest" ,
"typeVersion" : 4 ,
"position" : [ 450 , 200 ] ,
"parameters" : {
"url" : "http://host.docker.internal:4000/query/freescout" ,
"method" : "POST" ,
"headers" : {
"Content-Type" : "application/json"
} ,
"sendBody" : true ,
"specifyBody" : "json" ,
2026-03-17 10:41:48 +01:00
"jsonBody" : "{\"query\":\"SELECT c.id, c.number, c.subject, c.customer_email, c.status, GROUP_CONCAT(t.body SEPARATOR ',') as threads_text FROM conversations c LEFT JOIN threads t ON c.id = t.conversation_id LEFT JOIN conversation_custom_field ccf ON c.id = ccf.conversation_id AND ccf.custom_field_id = 8 WHERE c.status = 1 AND ccf.id IS NULL GROUP BY c.id LIMIT 20\"}"
2026-03-17 09:31:03 +01:00
}
} ,
2026-03-17 17:27:49 +01:00
{
"id" : "uuid-split-out" ,
"name" : "Split Array into Items" ,
"type" : "n8n-nodes-base.splitOut" ,
"typeVersion" : 1 ,
"position" : [ 650 , 200 ] ,
"parameters" : {
"fieldToSplitOut" : "data" ,
"options" : { }
}
} ,
2026-03-17 09:31:03 +01:00
{
"id" : "uuid-extract-data" ,
"name" : "Extract Conversation Data" ,
2026-03-17 17:27:49 +01:00
"type" : "n8n-nodes-base.code" ,
"typeVersion" : 2 ,
"position" : [ 850 , 200 ] ,
2026-03-17 09:31:03 +01:00
"parameters" : {
2026-03-17 17:27:49 +01:00
"mode" : "runOnceForEachItem" ,
"jsCode" : "const item = $input.item.json;\n// HTML-Tags entfernen damit die AI lesbaren Text bekommt\nconst rawText = item.threads_text || 'Keine Beschreibung vorhanden';\nconst plainText = rawText\n .replace(/<[^>]+>/g, ' ')\n .replace(/ /g, ' ')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/"/g, '\"')\n .replace(/\\s+/g, ' ')\n .trim()\n .substring(0, 2000);\nreturn { json: {\n ticket_id: item.id,\n ticket_number: item.number,\n subject: item.subject,\n customer_email: item.customer_email,\n problem_text: plainText\n}};"
2026-03-17 09:31:03 +01:00
}
} ,
{
"id" : "uuid-llm-analyze" ,
"name" : "LiteLLM AI Analysis" ,
"type" : "n8n-nodes-base.httpRequest" ,
"typeVersion" : 4 ,
2026-03-17 17:27:49 +01:00
"position" : [ 1050 , 200 ] ,
2026-03-17 09:31:03 +01:00
"parameters" : {
"url" : "http://llm.eks-ai.apps.asgard.eks-lnx.fft-it.de/v1/chat/completions" ,
"method" : "POST" ,
"headers" : {
"Content-Type" : "application/json"
} ,
"sendBody" : true ,
"specifyBody" : "json" ,
2026-03-17 17:27:49 +01:00
"jsonBody" : "={{ JSON.stringify({model: 'gpt-oss_120b_128k-gpu', messages: [{role: 'system', content: 'Du bist ein IT-Support-Assistent. Analysiere das folgende IT-Support-Ticket und gib eine strukturierte JSON-Antwort mit folgenden Feldern: kategorie (z.B. Hardware, Software, Netzwerk, Zugriff), lösung_typ (BARAMUNDI_JOB, AUTOMATISCHE_ANTWORT, oder ESKALATION), vertrauen (Dezimal zwischen 0.0 und 1.0 - wie sicher bist du bei dieser Lösung), baramundi_job (Name des Jobs falls BARAMUNDI_JOB), antwort_text (Die Antwort an den Nutzer), begründung (Kurze Erklärung deiner Analyse)'}, {role: 'user', content: 'Ticket-Nummer: ' + $json.ticket_number + '\\nBetreff: ' + $json.subject + '\\nProblembeschreibung:\\n' + $json.problem_text + '\\n\\nBitte antworte NUR mit gültiger JSON in dieser Struktur: {\"kategorie\": \"...\", \"lösung_typ\": \"...\", \"vertrauen\": 0.75, \"baramundi_job\": \"...\", \"antwort_text\": \"...\", \"begründung\": \"...\"}'}], temperature: 0.7, max_tokens: 1000}) }}"
2026-03-17 09:31:03 +01:00
}
} ,
{
"id" : "uuid-parse-response" ,
"name" : "Parse AI Response" ,
2026-03-17 17:27:49 +01:00
"type" : "n8n-nodes-base.code" ,
"typeVersion" : 2 ,
"position" : [ 1250 , 200 ] ,
2026-03-17 09:31:03 +01:00
"parameters" : {
2026-03-17 17:27:49 +01:00
"mode" : "runOnceForEachItem" ,
"jsCode" : "const content = $input.item.json.choices[0].message.content;\nconst extractData = $('Extract Conversation Data').item.json;\nconst ticketId = extractData.ticket_id !== undefined ? extractData.ticket_id : extractData.id;\nlet vertrauen = 0.1;\nlet loesung_typ = 'UNBEKANNT';\nlet kategorie = '';\nlet antwort_text = '';\nlet baramundi_job = '';\ntry {\n const parsed = JSON.parse(content);\n vertrauen = typeof parsed.vertrauen === 'number' ? parsed.vertrauen : 0.1;\n loesung_typ = parsed['lösung_typ'] || parsed.loesung_typ || 'UNBEKANNT';\n kategorie = parsed.kategorie || '';\n antwort_text = parsed.antwort_text || '';\n baramundi_job = parsed.baramundi_job || '';\n} catch(e) { vertrauen = 0.1; }\n// Human-readable for Freescout textarea\nconst lines = [loesung_typ + ' | Vertrauen: ' + vertrauen + ' | Kategorie: ' + kategorie];\nif (baramundi_job) lines.push('Baramundi-Job: ' + baramundi_job);\nlines.push('---');\nlines.push(antwort_text);\nconst display_text = lines.join(' | ');\n// SQL-safe: Quotes escapen, Zeilenumbrüche als ¶ (Pilcrow) erhalten damit\n// Workflow B die Struktur der KI-Antwort wiederherstellen kann.\nconst ai_content_sql = display_text.replace(/'/g, \"''\").replace(/\\r/g, '').replace(/\\n/g, '¶');\nconst ai_json_sql = content.replace(/'/g, \"''\").replace(/[\\n\\r]/g, ' ');\nreturn { json: { vertrauen, ticket_id: ticketId, ai_content: content, ai_content_sql, ai_json_sql } };"
}
} ,
{
"id" : "uuid-check-confidence" ,
"name" : "Check Confidence >= 0.6" ,
"type" : "n8n-nodes-base.if" ,
"typeVersion" : 2 ,
"position" : [ 1450 , 200 ] ,
"parameters" : {
"conditions" : {
"options" : {
"caseSensitive" : true ,
"leftValue" : "" ,
"typeValidation" : "loose"
} ,
"conditions" : [
2026-03-17 11:45:03 +01:00
{
2026-03-17 17:27:49 +01:00
"id" : "cond-confidence" ,
"leftValue" : "={{ $json.vertrauen }}" ,
"rightValue" : 0.6 ,
"operator" : {
"type" : "number" ,
"operation" : "gte"
}
2026-03-17 11:45:03 +01:00
}
2026-03-17 17:27:49 +01:00
] ,
"combinator" : "and"
2026-03-17 11:45:03 +01:00
}
2026-03-17 09:31:03 +01:00
}
} ,
{
2026-03-17 17:27:49 +01:00
"id" : "uuid-save-ai-suggestion" ,
"name" : "Save AI Suggestion (field 6)" ,
"type" : "n8n-nodes-base.httpRequest" ,
"typeVersion" : 4 ,
"position" : [ 1650 , 100 ] ,
"parameters" : {
"url" : "http://host.docker.internal:4000/query/freescout" ,
"method" : "POST" ,
"headers" : {
"Content-Type" : "application/json"
} ,
"sendBody" : true ,
"specifyBody" : "json" ,
"jsonBody" : "={{ JSON.stringify({query: \"INSERT INTO conversation_custom_field (conversation_id, custom_field_id, value) VALUES (\" + $json.ticket_id + \", 6, '\" + $json.ai_content_sql + \"') ON DUPLICATE KEY UPDATE value = VALUES(value)\"}) }}"
}
} ,
{
"id" : "uuid-save-status-pending" ,
"name" : "Save Status PENDING (field 7)" ,
"type" : "n8n-nodes-base.httpRequest" ,
"typeVersion" : 4 ,
"position" : [ 1650 , 200 ] ,
2026-03-17 09:31:03 +01:00
"parameters" : {
2026-03-17 17:27:49 +01:00
"url" : "http://host.docker.internal:4000/query/freescout" ,
"method" : "POST" ,
"headers" : {
"Content-Type" : "application/json"
} ,
"sendBody" : true ,
"specifyBody" : "json" ,
"jsonBody" : "={{ JSON.stringify({query: \"INSERT INTO conversation_custom_field (conversation_id, custom_field_id, value) VALUES (\" + $('Parse AI Response').item.json.ticket_id + \", 7, '0') ON DUPLICATE KEY UPDATE value = VALUES(value)\"}) }}"
2026-03-17 09:31:03 +01:00
}
} ,
{
2026-03-17 17:27:49 +01:00
"id" : "uuid-save-processed-flag" ,
"name" : "Save Processed Flag (field 8)" ,
2026-03-17 09:31:03 +01:00
"type" : "n8n-nodes-base.httpRequest" ,
"typeVersion" : 4 ,
2026-03-17 17:27:49 +01:00
"position" : [ 1650 , 300 ] ,
2026-03-17 09:31:03 +01:00
"parameters" : {
"url" : "http://host.docker.internal:4000/query/freescout" ,
"method" : "POST" ,
"headers" : {
"Content-Type" : "application/json"
} ,
"sendBody" : true ,
"specifyBody" : "json" ,
2026-03-17 17:27:49 +01:00
"jsonBody" : "={{ JSON.stringify({query: \"INSERT INTO conversation_custom_field (conversation_id, custom_field_id, value) VALUES (\" + $('Parse AI Response').item.json.ticket_id + \", 8, '1') ON DUPLICATE KEY UPDATE value = VALUES(value)\"}) }}"
2026-03-17 09:31:03 +01:00
}
} ,
{
"id" : "uuid-no-action" ,
"name" : "Skip - Low Confidence" ,
"type" : "n8n-nodes-base.set" ,
"typeVersion" : 3 ,
2026-03-17 17:27:49 +01:00
"position" : [ 1650 , 350 ] ,
2026-03-17 09:31:03 +01:00
"parameters" : {
2026-03-17 17:27:49 +01:00
"mode" : "manual" ,
2026-03-17 09:31:03 +01:00
"options" : { } ,
"assignments" : {
"assignments" : [
{
2026-03-17 17:27:49 +01:00
"id" : "assign-skipped" ,
2026-03-17 09:31:03 +01:00
"name" : "skipped" ,
"value" : true ,
"type" : "boolean"
} ,
{
2026-03-17 17:27:49 +01:00
"id" : "assign-reason" ,
2026-03-17 09:31:03 +01:00
"name" : "reason" ,
2026-03-17 17:27:49 +01:00
"value" : "={{ 'Confidence ' + $json.vertrauen + ' < 0.6' }}" ,
2026-03-17 09:31:03 +01:00
"type" : "string"
}
]
}
}
}
] ,
"connections" : {
"Trigger" : {
"main" : [
2026-03-17 17:27:49 +01:00
[ { "node" : "Get Unprocessed Conversations" , "index" : 0 } ]
2026-03-17 09:31:03 +01:00
]
} ,
"Get Unprocessed Conversations" : {
"main" : [
2026-03-17 17:27:49 +01:00
[ { "node" : "Split Array into Items" , "index" : 0 } ]
]
} ,
"Split Array into Items" : {
"main" : [
[ { "node" : "Extract Conversation Data" , "index" : 0 } ]
2026-03-17 09:31:03 +01:00
]
} ,
"Extract Conversation Data" : {
"main" : [
2026-03-17 17:27:49 +01:00
[ { "node" : "LiteLLM AI Analysis" , "index" : 0 } ]
2026-03-17 09:31:03 +01:00
]
} ,
"LiteLLM AI Analysis" : {
"main" : [
2026-03-17 17:27:49 +01:00
[ { "node" : "Parse AI Response" , "index" : 0 } ]
2026-03-17 09:31:03 +01:00
]
} ,
"Parse AI Response" : {
"main" : [
2026-03-17 17:27:49 +01:00
[ { "node" : "Check Confidence >= 0.6" , "index" : 0 } ]
2026-03-17 09:31:03 +01:00
]
} ,
"Check Confidence >= 0.6" : {
"main" : [
2026-03-17 17:27:49 +01:00
[ { "node" : "Save AI Suggestion (field 6)" , "index" : 0 } ] ,
[ { "node" : "Skip - Low Confidence" , "index" : 0 } ]
]
} ,
"Save AI Suggestion (field 6)" : {
"main" : [
[ { "node" : "Save Status PENDING (field 7)" , "index" : 0 } ]
]
} ,
"Save Status PENDING (field 7)" : {
"main" : [
[ { "node" : "Save Processed Flag (field 8)" , "index" : 0 } ]
2026-03-17 09:31:03 +01:00
]
}
} ,
"active" : false ,
"settings" : {
"errorHandler" : "continueOnError"
}
}