This workflow corresponds to n8n.io template #15612 — we link there as the canonical source.
This workflow follows the Execute Workflow Trigger → HTTP Request 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 →
{
"id": "mfj3y9MNPLaxkcwU",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "attachmentsAnalyzer",
"tags": [],
"nodes": [
{
"id": "e342262d-24d0-4d50-bf5c-4b6df19561e2",
"name": "About",
"type": "n8n-nodes-base.stickyNote",
"position": [
640,
-192
],
"parameters": {
"color": 5,
"width": 1336,
"height": 544,
"content": "## Overview\n\nSubworkflow for analyzing Mattermost attachments before passing them to the AI Agent.\n\n## Per-attachment behavior\n- **Image** (mime starts with `image/`, size \u2264 `MAX_IMAGE_SIZE_BYTES`): downloaded and analyzed by OpenAI vision model.\n- **Text small** (text-like by mime/extension, size \u2264 `MAX_TEXT_SIZE_BYTES`): downloaded as plain text and inlined into the context block (with line count check \u2264 `MAX_TEXT_LINES`).\n- **Text too large** (text-like, exceeds byte or line limit): replaced with a \"too big\" marker so the agent asks the user for the relevant excerpt.\n- **Other** (binaries, oversized images, unknown): replaced with a generic \"not analyzed\" marker.\n\n## Setup checklist\n1. Set `MATTERMOST_BASE_URL` in the Config node to your Mattermost origin (`https://hostname`; no trailing slash).\n2. Create an `HTTP Header Auth` credential called \"Mattermost PAT\" with:\n - Name: `Authorization`\n - Value: `Bearer <your_personal_access_token>`\n3. Attach this credential to all three HTTP Request nodes (Get file info, Download file, Download text file).\n4. Attach your OpenAI credential to the \"OpenAI: Analyze Image\" node.\n5. Tune in Config: `VISION_MODEL` (OpenAI vision model id; the Analyze Image node reads it from Config), `MAX_IMAGE_SIZE_BYTES`, `MAX_TEXT_SIZE_BYTES`, `MAX_TEXT_LINES`.\n\n## Notes\n- The IF guard short-circuits when `file_ids` is empty so the parent workflow can call this subworkflow unconditionally.\n- Classification by mime + extension catches `.log` files served as `application/octet-stream`.\n- Final output is always a single item with the same shape \u2014 safe to consume in the parent."
},
"typeVersion": 1
},
{
"id": "bede9898-e490-496a-bf78-2703c31292e5",
"name": "When Executed by Another Workflow",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"position": [
704,
656
],
"parameters": {
"inputSource": "jsonExample",
"jsonExample": "{\n \"file_ids\": [\"x4t1k7example1\", \"9bzqmexample2\"]\n}"
},
"typeVersion": 1.1
},
{
"id": "2cbd0079-0953-4cfd-ba09-89b130e3e065",
"name": "Config",
"type": "n8n-nodes-base.set",
"position": [
896,
656
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "c1f+1234567890-+1234567890",
"name": "MATTERMOST_BASE_URL",
"type": "string",
"value": "https://<your-mattermost-host>"
},
{
"id": "c1f+1234567890-+1234567890",
"name": "VISION_MODEL",
"type": "string",
"value": "gpt-4o-mini"
},
{
"id": "c1f+1234567890-+1234567890",
"name": "MAX_IMAGE_SIZE_BYTES",
"type": "number",
"value": 20971520
},
{
"id": "c1f+1234567890-+1234567890",
"name": "MAX_TEXT_SIZE_BYTES",
"type": "number",
"value": 65536
},
{
"id": "c1f+1234567890-+1234567890",
"name": "MAX_TEXT_LINES",
"type": "number",
"value": 300
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "50298b55-4fbf-4052-8859-3ae9f1d73c23",
"name": "Has attachments?",
"type": "n8n-nodes-base.if",
"position": [
1088,
656
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "if+1234567890-+1234567890",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ ($json.file_ids || []).length }}",
"rightValue": 0
}
]
}
},
"typeVersion": 2.2
},
{
"id": "7369a49e-7f79-4082-a07b-1ef09b87f6f4",
"name": "Empty result",
"type": "n8n-nodes-base.set",
"position": [
1296,
912
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "se+1234567890-+1234567890",
"name": "attachments_context",
"type": "string",
"value": ""
},
{
"id": "se+1234567890-+1234567890",
"name": "attachments_count",
"type": "number",
"value": 0
}
]
}
},
"typeVersion": 3.4
},
{
"id": "08c9ba1a-6413-4a5c-a129-6fa456d5e907",
"name": "Split file_ids",
"type": "n8n-nodes-base.code",
"position": [
1328,
464
],
"parameters": {
"jsCode": "// Split incoming file_ids array into one item per file_id.\n// The IF guard upstream guarantees at least one id; defensive check kept.\n\nconst data = $input.first().json;\nconst ids = Array.isArray(data.file_ids) ? data.file_ids : [];\n\nreturn ids.map((file_id, idx) => ({\n json: {\n file_id,\n file_index: idx\n }\n}));"
},
"typeVersion": 2
},
{
"id": "1e0aae90-4e03-4f2d-9639-0c15bbd7aff8",
"name": "Get file info",
"type": "n8n-nodes-base.httpRequest",
"position": [
1552,
464
],
"parameters": {
"url": "={{ $('Config').first().json.MATTERMOST_BASE_URL }}/api/v4/files/{{ $json.file_id }}/info",
"options": {},
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "1b35f66f-fef6-4e36-9afe-4bd2408cda3c",
"name": "Classify file",
"type": "n8n-nodes-base.code",
"position": [
1760,
464
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Classify the attachment by mime type, file extension, and size.\n// Adds a `category` field used by the downstream Switch node:\n// - 'image' image, fits MAX_IMAGE_SIZE_BYTES \u2192 vision pipeline\n// - 'text_small' text-like, fits MAX_TEXT_SIZE_BYTES \u2192 inline content\n// - 'text_too_large' text-like but exceeds MAX_TEXT_SIZE_BYTES \u2192 \"too big\" marker\n// - 'unsupported' binaries, oversized images, unknown formats\n//\n// Detecting text by extension is intentional: Mattermost often serves `.log`\n// (and other dev-related files) as `application/octet-stream`.\n\nconst fileInfo = $input.item.json;\nconst config = $('Config').first().json;\n\nconst mimeType = (fileInfo.mime_type || '').toLowerCase();\nconst fileName = fileInfo.name || '';\nconst size = Number(fileInfo.size) || 0;\n\nconst dotIdx = fileName.lastIndexOf('.');\nconst ext = dotIdx >= 0 ? fileName.slice(dotIdx + 1).toLowerCase() : '';\n\nconst TEXT_EXTENSIONS = new Set([\n // Logs & plain text\n 'log', 'txt', 'out', 'err',\n // Structured data\n 'json', 'ndjson', 'jsonl', 'yaml', 'yml', 'xml', 'csv', 'tsv', 'toml',\n // Docs & configs\n 'md', 'markdown', 'rst', 'conf', 'cfg', 'ini', 'env', 'properties',\n // Shell & scripts\n 'sh', 'bash', 'zsh', 'fish', 'ps1',\n // Programming languages\n 'py', 'js', 'mjs', 'cjs', 'ts', 'jsx', 'tsx',\n 'go', 'rs', 'java', 'kt', 'rb', 'php', 'pl', 'lua',\n 'c', 'h', 'cpp', 'hpp', 'cc', 'cs', 'm', 'mm',\n // Infra & DevOps\n 'tf', 'hcl', 'tfvars', 'sql', 'graphql', 'gql', 'dockerfile',\n // Web\n 'html', 'htm', 'css', 'scss', 'sass', 'less', 'vue', 'svelte',\n // Patches\n 'patch', 'diff'\n]);\n\nconst isImage = mimeType.startsWith('image/');\nconst isTextMime =\n mimeType.startsWith('text/') ||\n mimeType === 'application/json' ||\n mimeType === 'application/ld+json' ||\n mimeType === 'application/xml' ||\n mimeType === 'application/x-yaml' ||\n mimeType === 'application/yaml' ||\n mimeType === 'application/x-sh' ||\n mimeType === 'application/javascript' ||\n mimeType === 'application/x-ndjson' ||\n mimeType === 'application/toml';\nconst isTextLike = isTextMime || TEXT_EXTENSIONS.has(ext);\n\nlet category;\nif (isImage && size <= config.MAX_IMAGE_SIZE_BYTES) {\n category = 'image';\n} else if (isTextLike && size <= config.MAX_TEXT_SIZE_BYTES) {\n category = 'text_small';\n} else if (isTextLike) {\n category = 'text_too_large';\n} else {\n category = 'unsupported';\n}\n\nreturn {\n json: {\n ...fileInfo,\n file_extension: ext,\n is_text_like: isTextLike,\n is_image: isImage,\n category\n }\n};"
},
"typeVersion": 2
},
{
"id": "d7abba2a-4351-4bae-8ea5-cc1c45335d9c",
"name": "Route by category",
"type": "n8n-nodes-base.switch",
"position": [
1968,
432
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "image",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "sw+1234567890-+1234567890",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.category }}",
"rightValue": "image"
}
]
},
"renameOutput": true
},
{
"outputKey": "text_small",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "sw+1234567890-+1234567890",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.category }}",
"rightValue": "text_small"
}
]
},
"renameOutput": true
},
{
"outputKey": "text_too_large",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "sw+1234567890-+1234567890",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.category }}",
"rightValue": "text_too_large"
}
]
},
"renameOutput": true
}
]
},
"options": {
"fallbackOutput": "extra",
"renameFallbackOutput": "unsupported"
}
},
"typeVersion": 3.2
},
{
"id": "51e2994b-61c6-4797-b521-616f6005f53d",
"name": "Download file",
"type": "n8n-nodes-base.httpRequest",
"position": [
2192,
160
],
"parameters": {
"url": "={{ $('Config').first().json.MATTERMOST_BASE_URL }}/api/v4/files/{{ $json.id }}",
"options": {
"response": {
"response": {
"responseFormat": "file"
}
}
},
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "720bd88c-5091-4b38-a58f-488106b3b067",
"name": "OpenAI: Analyze Image",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
2416,
160
],
"parameters": {
"text": "You are a DevOps screenshot analyst. Produce a concise structured description in English of the attached image.\n\nOutput sections (skip a section if not applicable):\n1. source_system \u2014 which tool/system the screenshot is from. Choose from: GitHub Actions, GitLab CI, Jenkins, TeamCity, Grafana, Sentry, Loki, Prometheus, Mattermost, Slack, Jira, Kubernetes Dashboard, terminal/shell, IDE (VSCode/JetBrains), browser DevTools, generic web page, unknown.\n2. visible_text \u2014 verbatim transcript of error messages, stack traces, command output, status codes, job names. Preserve exact wording and casing. Use code fences for multi-line blocks.\n3. key_signals \u2014 bullet list of what matters for an SRE: failed step name, exit code, service/pod/namespace, timestamp, severity, status indicator color.\n4. summary \u2014 1-2 sentences in plain English on what the screenshot shows.\n\nBe terse. No filler, no greetings, no markdown headings beyond what is asked. If the image is not technical (meme, photo of a person, decorative), say so in summary and skip other sections.",
"modelId": {
"__rl": true,
"mode": "id",
"value": "={{ $('Config').first().json.VISION_MODEL }}"
},
"options": {
"detail": "auto"
},
"resource": "image",
"inputType": "=base64",
"operation": "analyze"
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.8
},
{
"id": "563c042b-8359-4eed-b5fa-32ea62da3a19",
"name": "Format image description",
"type": "n8n-nodes-base.code",
"position": [
2640,
160
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// runOnceForEachItem \u2014 pairedItem propagation lets us reach back to\n// \"Classify file\" by item index and recover the original file metadata.\n\nconst llmOut = $input.item.json;\nconst fileInfo = $('Classify file').item.json;\n\n// OpenAI Analyze Image returns the model text under different fields\n// depending on n8n version. Try common shapes in order.\nlet description =\n (typeof llmOut.content === 'string' && llmOut.content) ||\n (typeof llmOut.message?.content === 'string' && llmOut.message.content) ||\n (Array.isArray(llmOut.content) && llmOut.content.map(c => c.text || '').join('\\n')) ||\n llmOut.text ||\n '[vision model returned no content]';\n\nif (typeof description !== 'string') {\n description = JSON.stringify(description);\n}\n\nreturn {\n json: {\n file_id: fileInfo.id,\n file_name: fileInfo.name,\n mime_type: fileInfo.mime_type,\n size: fileInfo.size,\n is_image: true,\n is_text: false,\n description\n }\n};"
},
"typeVersion": 2
},
{
"id": "9b8f65b0-012a-4d17-a53c-65931b5e7cf2",
"name": "Download text file",
"type": "n8n-nodes-base.httpRequest",
"position": [
2192,
352
],
"parameters": {
"url": "={{ $('Config').first().json.MATTERMOST_BASE_URL }}/api/v4/files/{{ $json.id }}",
"options": {
"response": {
"response": {
"responseFormat": "text"
}
}
},
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "eb150e5f-ddb7-4387-b8c8-55ffd741f22d",
"name": "Format text content",
"type": "n8n-nodes-base.code",
"position": [
2416,
352
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// runOnceForEachItem. HTTP Request (responseFormat=text) returned the body\n// as a string in $json.data. File metadata is recovered via pairedItem from\n// \"Classify file\".\n//\n// Two-tier size check:\n// tier 1 (bytes): already enforced by \"Classify file\" + Switch routing.\n// tier 2 (lines): re-checked here for files that fit byte budget but\n// contain unusually long lines (e.g. minified JSON).\n\nconst fileInfo = $('Classify file').item.json;\nconst config = $('Config').first().json;\nconst text = typeof $json.data === 'string' ? $json.data : '';\n\nconst lineCount = text.length === 0 ? 0 : text.split(/\\r\\n|\\r|\\n/).length;\nconst maxLines = Number(config.MAX_TEXT_LINES) || 300;\n\nif (lineCount > maxLines) {\n return {\n json: {\n file_id: fileInfo.id,\n file_name: fileInfo.name,\n mime_type: fileInfo.mime_type,\n size: fileInfo.size,\n is_image: false,\n is_text: true,\n description: `Attachment ${fileInfo.name} is too large to inline: ${fileInfo.size} bytes / ${lineCount} lines (limit: ${maxLines} lines). Ask the user to share the relevant excerpt.`\n }\n };\n}\n\nreturn {\n json: {\n file_id: fileInfo.id,\n file_name: fileInfo.name,\n mime_type: fileInfo.mime_type,\n size: fileInfo.size,\n is_image: false,\n is_text: true,\n description: `Text content of ${fileInfo.name} (${fileInfo.size} bytes, ${lineCount} lines):\\n\\n\\`\\`\\`\\n${text}\\n\\`\\`\\``\n }\n};"
},
"typeVersion": 2
},
{
"id": "bf4dfecb-8585-4252-871f-dfee7970dc04",
"name": "Text too large marker",
"type": "n8n-nodes-base.set",
"position": [
2192,
576
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "tl+1234567890-+1234567890",
"name": "file_id",
"type": "string",
"value": "={{ $json.id }}"
},
{
"id": "tl+1234567890-+1234567890",
"name": "file_name",
"type": "string",
"value": "={{ $json.name }}"
},
{
"id": "tl+1234567890-+1234567890",
"name": "mime_type",
"type": "string",
"value": "={{ $json.mime_type }}"
},
{
"id": "tl+1234567890-+1234567890",
"name": "size",
"type": "number",
"value": "={{ $json.size }}"
},
{
"id": "tl+1234567890-+1234567890",
"name": "is_image",
"type": "boolean",
"value": false
},
{
"id": "tl+1234567890-+1234567890",
"name": "is_text",
"type": "boolean",
"value": true
},
{
"id": "tl+1234567890-+1234567890",
"name": "description",
"type": "string",
"value": "=Attachment {{ $json.name }} is a text file but too large to inline: {{ $json.size }} bytes (limit: {{ $('Config').first().json.MAX_TEXT_SIZE_BYTES }} bytes). Ask the user to share the relevant excerpt."
}
]
}
},
"typeVersion": 3.4
},
{
"id": "63ec86c3-eb4b-4da3-bbc7-c80293618a44",
"name": "Non-image marker",
"type": "n8n-nodes-base.set",
"position": [
2192,
784
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "ni+1234567890-+1234567890",
"name": "file_id",
"type": "string",
"value": "={{ $json.id }}"
},
{
"id": "ni+1234567890-+1234567890",
"name": "file_name",
"type": "string",
"value": "={{ $json.name }}"
},
{
"id": "ni+1234567890-+1234567890",
"name": "mime_type",
"type": "string",
"value": "={{ $json.mime_type }}"
},
{
"id": "ni+1234567890-+1234567890",
"name": "size",
"type": "number",
"value": "={{ $json.size }}"
},
{
"id": "ni+1234567890-+1234567890",
"name": "is_image",
"type": "boolean",
"value": "={{ ($json.mime_type || '').startsWith('image/') }}"
},
{
"id": "ni+1234567890-+1234567890",
"name": "is_text",
"type": "boolean",
"value": false
},
{
"id": "ni+1234567890-+1234567890",
"name": "description",
"type": "string",
"value": "=Attachment {{ $json.name }} not analyzed (mime: {{ $json.mime_type }}, size: {{ $json.size }} bytes). Either unsupported binary format or exceeds size limit."
}
]
}
},
"typeVersion": 3.4
},
{
"id": "f716620e-b4d8-45fb-b080-aa311dc79d43",
"name": "Merge",
"type": "n8n-nodes-base.merge",
"position": [
2864,
528
],
"parameters": {
"numberInputs": 4
},
"typeVersion": 3.2
},
{
"id": "5cbc7aa3-6eae-4e39-8363-e7fa7ca8efb9",
"name": "Aggregate",
"type": "n8n-nodes-base.aggregate",
"position": [
3072,
560
],
"parameters": {
"options": {},
"aggregate": "aggregateAllItemData",
"destinationFieldName": "attachments"
},
"typeVersion": 1
},
{
"id": "3de99b38-8fec-432f-9f9c-2fcfcff19d5b",
"name": "Build attachments_context",
"type": "n8n-nodes-base.code",
"position": [
3264,
560
],
"parameters": {
"jsCode": "// Aggregate produced a single item with `attachments: [...]`.\n// Build a human-readable block for the AI Agent system prompt.\n// Output shape mirrors the \"Empty result\" Set node so the parent workflow\n// can consume the output uniformly.\n\nconst attachments = $input.first().json.attachments || [];\nconst valid = attachments.filter(a => a && a.file_id);\n\nif (valid.length === 0) {\n return [{ json: { attachments_context: '', attachments_count: 0 } }];\n}\n\nconst blocks = valid.map((a, i) => {\n let tag;\n if (a.is_image) tag = 'image';\n else if (a.is_text) tag = 'text';\n else tag = a.mime_type || 'file';\n\n const header = `Attachment ${i + 1}: ${a.file_name} [${tag}]`;\n return `${header}\\n${a.description}`;\n});\n\nreturn [{\n json: {\n attachments_context: blocks.join('\\n\\n---\\n\\n'),\n attachments_count: valid.length\n }\n}];"
},
"typeVersion": 2
},
{
"id": "70b6af74-e8da-464a-b47c-ac1426a65865",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
640,
576
],
"parameters": {
"width": 576,
"height": 480,
"content": "# Input chain\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n```json\n{ \"file_ids\": [\"id1\", \"id2\", ...] }\n```\n"
},
"typeVersion": 1
},
{
"id": "c830d6bb-75fb-4004-8a15-2b81efc5f44b",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
3008,
480
],
"parameters": {
"width": 528,
"height": 512,
"content": "## Output chain\n\n\n\n\n\n\n\n\n\n\n\n\n```json\n{\n \"attachments_context\": \"...human-readable block...\",\n \"attachments_count\": 2\n}\n```"
},
"typeVersion": 1
},
{
"id": "91fa12c8-0d2f-4880-88c8-b011a67a40fe",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
2160,
48
],
"parameters": {
"color": 6,
"width": 624,
"height": 944,
"content": "## Attachment analyzer\nAccepts images and text files"
},
"typeVersion": 1
}
],
"active": true,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "8ce4e88e-800c-4a8d-b9a1-bdb69f3d86bf",
"connections": {
"Merge": {
"main": [
[
{
"node": "Aggregate",
"type": "main",
"index": 0
}
]
]
},
"Config": {
"main": [
[
{
"node": "Has attachments?",
"type": "main",
"index": 0
}
]
]
},
"Aggregate": {
"main": [
[
{
"node": "Build attachments_context",
"type": "main",
"index": 0
}
]
]
},
"Classify file": {
"main": [
[
{
"node": "Route by category",
"type": "main",
"index": 0
}
]
]
},
"Download file": {
"main": [
[
{
"node": "OpenAI: Analyze Image",
"type": "main",
"index": 0
}
]
]
},
"Get file info": {
"main": [
[
{
"node": "Classify file",
"type": "main",
"index": 0
}
]
]
},
"Split file_ids": {
"main": [
[
{
"node": "Get file info",
"type": "main",
"index": 0
}
]
]
},
"Has attachments?": {
"main": [
[
{
"node": "Split file_ids",
"type": "main",
"index": 0
}
],
[
{
"node": "Empty result",
"type": "main",
"index": 0
}
]
]
},
"Non-image marker": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 3
}
]
]
},
"Route by category": {
"main": [
[
{
"node": "Download file",
"type": "main",
"index": 0
}
],
[
{
"node": "Download text file",
"type": "main",
"index": 0
}
],
[
{
"node": "Text too large marker",
"type": "main",
"index": 0
}
],
[
{
"node": "Non-image marker",
"type": "main",
"index": 0
}
]
]
},
"Download text file": {
"main": [
[
{
"node": "Format text content",
"type": "main",
"index": 0
}
]
]
},
"Format text content": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
}
]
]
},
"OpenAI: Analyze Image": {
"main": [
[
{
"node": "Format image description",
"type": "main",
"index": 0
}
]
]
},
"Text too large marker": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 2
}
]
]
},
"Format image description": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
}
]
]
},
"When Executed by Another Workflow": {
"main": [
[
{
"node": "Config",
"type": "main",
"index": 0
}
]
]
}
}
}
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.
httpHeaderAuthopenAiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Subworkflow for analyzing Mattermost attachments. Image (mime starts with , size ≤ ): downloaded and analyzed by OpenAI vision model. Text small (text-like by mime/extension, size ≤ ): downloaded as plain text and inlined into the context block (with line count check ≤ ). Text…
Source: https://n8n.io/workflows/15612/ — 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.
How it Works
The best content automation template in the market is now even better—with “deep research” on time-sensitive topics\! Unlike most n8n content automation templates that are mainly for “demo purposes,”
Template Carnaval - time instagram. Uses toolWorkflow, lmChatOpenAi, memoryBufferWindow, agent. Event-driven trigger; 56 nodes.
This workflow is a fully automated YouTube Shorts production pipeline. It takes the structured output from a video digestion workflow (transcript, key moments, metadata) and produces finished, rendere
Code Schedule. Uses memoryBufferWindow, agent, stickyNote, outputParserStructured. Event-driven trigger; 45 nodes.