{"workflow":{"id":14520,"name":"Generate GitLab release notes from Linear issues with Claude Opus","views":2,"recentViews":1,"totalViews":2,"createdAt":"2026-03-31T09:25:38.055Z","description":"## 📝 Release Note Helper\n\nTriggered by a GitLab MR webhook, this workflow automatically assists your team in writing **customer-facing release notes** by combining Linear issue data with Claude AI.\n\nApply the `rn-release-n8n` label to any release note MR in your docs repository to trigger it.\n\n## How it works\n\n1. **Version detection** — reads your release RSS feed to find the last published version, then fetches all matching Linear version labels created since then to determine the version range automatically\n2. **Issue collection** — queries Linear for all completed issues in that version range that have Zendesk tickets, Slack links, or custom labels (`Customer request`, `Release note public`) attached\n3. **Ticket summary** — posts a structured list of all relevant issues to the MR as a comment\n4. **AI draft** — sends issue details to Claude, which generates customer-facing changelog entries grouped into `### Enhancements` and `### Fixes`, posted as a second MR comment\n5. **Done label** — adds `rn-done` to the MR when complete to prevent re-runs\n\n## Setup\n\n1. Configure a **GitLab webhook** on your docs repo pointing to this workflow's URL (Merge Request events)\n2. Create two labels on your GitLab repo: `rn-release-n8n` (to trigger) and `rn-done` (auto-applied on completion)\n3. Update the **RSS Read** node URL to your release RSS feed\n4. Replace `YOUR_PROJECT_ID` in all GitLab API nodes with your docs project ID\n5. Replace `YOUR_WORKSPACE` in the Code nodes with your Linear workspace slug\n6. Connect **Linear API**, **GitLab API**, and **Anthropic API** credentials\n\n## Notes\n\n- Versioning assumes a `vX.Y` Linear label convention — adapt the **Format labels** node for your own scheme\n- The AI prompt in **Message a model** is ready to use but can be customised to match your tone and changelog format\n- Issues are filtered to those with Zendesk, Slack attachments, or your custom labels — adjust in **Set Params**","workflow":{"id":"arv1gHOXJLJFgkjW","meta":{"instanceId":"21b41c2deb1c9e3f543253a0aa6a6e2c7bd7ef6bab90ffd478aa947c17d3b352","templateCredsSetupCompleted":true},"name":"Release Note Helper (Linear + GitLab + Claude AI)","tags":[{"id":"NyHsy31Uw9Ue7FPB","name":"gitlab","createdAt":"2023-10-20T10:07:13.343Z","updatedAt":"2023-10-20T10:07:13.343Z"},{"id":"6Ek7V8f4xbM9vWLj","name":"linear","createdAt":"2024-11-08T12:12:15.330Z","updatedAt":"2024-11-08T12:12:15.330Z"},{"id":"C7P3CMZEIzF0SZIQ","name":"claude","createdAt":"2026-03-31T09:22:32.906Z","updatedAt":"2026-03-31T09:22:32.906Z"}],"nodes":[{"id":"2cf5b983-8aa1-4e1b-ac83-2bb03ab09379","name":"Sticky Note","type":"n8n-nodes-base.stickyNote","position":[976,-480],"parameters":{"width":480,"height":816,"content":"## Release Note Helper (Linear + GitLab + Claude AI)\n\n### How it works\n\n1. Triggers on a GitLab merge request (MR) event to start the process.\n2. Checks existing MR conditions to determine if a release label is present.\n3. Retrieves and formats labels from a Linear RSS feed.\n4. Based on label presence, gathers Linear issues to generate summaries and suggestions.\n5. Applies AI to enhance MR summaries and updates the MR with suggestions and labels.\n6. Completes the MR process with additional notes if needed.\n\n### Setup steps\n\n- [ ] Configure GitLab credentials for API access.\n- [ ] Set up Linear API credentials for issue retrieval.\n- [ ] Ensure access to the AI model service for generating summaries.\n\n### Customization\n\nAdjust conditions and API endpoints for different GitLab projects or Linear workspaces."},"typeVersion":1},{"id":"1eeda314-34c9-41d5-9f35-fcb28a6574b5","name":"Sticky Note1","type":"n8n-nodes-base.stickyNote","position":[1536,-176],"parameters":{"color":7,"width":416,"height":320,"content":"## Trigger and initial condition\n\nStarts the workflow when a merge request is opened or updated in GitLab, then checks if the MR already contains release note inputs."},"typeVersion":1},{"id":"f81f9aaf-5c9d-4ff8-bcf5-92e900d24f2e","name":"Sticky Note2","type":"n8n-nodes-base.stickyNote","position":[1984,-144],"parameters":{"color":7,"width":416,"height":304,"content":"## Check and read RSS for labels\n\nEvaluates if a release label exists and reads relevant RSS feed to get initial labels."},"typeVersion":1},{"id":"920f1765-e0c7-4a78-aa6f-faded7855986","name":"Sticky Note3","type":"n8n-nodes-base.stickyNote","position":[2432,-144],"parameters":{"color":7,"width":864,"height":304,"content":"## Retrieve and process Linear labels\n\nFetches labels from Linear, extracts specific versions, and sets parameters for further processing."},"typeVersion":1},{"id":"8e8769a7-66b6-4d70-afee-c83f48cc94f1","name":"Sticky Note4","type":"n8n-nodes-base.stickyNote","position":[3328,-144],"parameters":{"color":7,"width":640,"height":304,"content":"## Get Linear issues and manage response\n\nRetrieves Linear issues and decides the subsequent action, including generating summaries or adding notes if issues are absent."},"typeVersion":1},{"id":"645b081d-a431-46f1-8928-fb91bcc4aedb","name":"Sticky Note5","type":"n8n-nodes-base.stickyNote","position":[3776,-480],"parameters":{"color":7,"width":480,"height":304,"content":"## Generate and add high level summary\n\nCreates a high-level summary and adds it to the GitLab MR."},"typeVersion":1},{"id":"10f93d46-1e67-4e78-88c9-495bff1b12e6","name":"Sticky Note6","type":"n8n-nodes-base.stickyNote","position":[4000,-128],"parameters":{"color":7,"width":992,"height":464,"content":"## AI suggestions and issue management\n\nGenerates AI-driven suggestions and applies labels, finalizing MR modifications with notes if necessary."},"typeVersion":1},{"id":"21b72b02-8876-4f41-b4c7-76bdde1cdd8b","name":"Set Various Fields","type":"n8n-nodes-base.set","position":[2928,-16],"parameters":{"values":{"string":[{"name":"versions_labels","value":"=[\"{{ $json.versionRange }}\"]"},{"name":"custom_labels","value":"[\"Customer request\", \"Release note public\"]"}]},"options":{}},"typeVersion":2},{"id":"4ac43eba-0e6f-4ebb-930c-647153070b55","name":"Generate Summary with Links","type":"n8n-nodes-base.code","position":[3824,-352],"parameters":{"jsCode":"const APPEND_LINKS = true;\n\n// --- Get label dates ---\nlet dateLabels = \"\";\ntry {\n    const formatLabels = $('Filter Labels Starting With v2')?.first()?.json;\n    const oldestDate = formatLabels?.oldestLabelDate;\n    const newestDate = formatLabels?.newestLabelDate;\n    dateLabels = (oldestDate && newestDate) ? `[${oldestDate} → ${newestDate}]` : \"\";\n} catch (error) {\n    dateLabels = \"\";\n}\n\n// --- Get version labels ---\nlet versionsLabels = [];\ntry {\n    const versionsLabelNodeOutput = $('Parse Linear Version Labels').first().json.versions_labels;\n    versionsLabels = typeof versionsLabelNodeOutput === 'string'\n        ? JSON.parse(versionsLabelNodeOutput)\n        : Array.isArray(versionsLabelNodeOutput)\n            ? versionsLabelNodeOutput\n            : [];\n} catch (error) {\n    versionsLabels = [];\n}\n\n// --- Get custom labels to include ---\nlet includeLabels = [];\ntry {\n    const includeLabelNodeOutput = $('Parse Linear Version Labels').first().json.custom_labels;\n    includeLabels = typeof includeLabelNodeOutput === 'string'\n        ? JSON.parse(includeLabelNodeOutput)\n        : Array.isArray(includeLabelNodeOutput)\n            ? includeLabelNodeOutput\n            : [];\n} catch (error) {\n    includeLabels = [];\n}\n\n// --- Initialize stats and grouping ---\nlet totalTickets = 0;\nlet ticketsWithZendesk = 0;\nlet ticketsWithCustomLabels = 0;\nconst issuesByTeam = {};\n\n// --- Slack link helper ---\nfunction extractFirstSlackLink(attachments) {\n    if (!attachments || !Array.isArray(attachments.nodes)) return null;\n    for (const att of attachments.nodes) {\n        if (att.url?.includes(\"your-company.slack.com/archives/\")) {\n            return att.url;\n        }\n    }\n    return null;\n}\n\n// --- Process each issue ---\nconst data = $input.first().json.data; // Get the original data\nconst items = data.issues.nodes;\nfor (const item of items) {\n    const issue = item;\n    totalTickets++;\n\n    const teamName = issue.team?.name || 'Unknown Team';\n    if (!issuesByTeam[teamName]) {\n        issuesByTeam[teamName] = [];\n    }\n    issuesByTeam[teamName].push(issue);\n\n    const zendeskAttachment = (issue.attachments?.nodes || []).find(att => att.url?.includes(\"your-company.zendesk.com\"));\n    if (zendeskAttachment) {\n        ticketsWithZendesk++;\n    }\n\n    const issueLabels = issue.labels?.nodes || [];\n    if (includeLabels.length > 0 && issueLabels.some(l => includeLabels.includes(l.name))) {\n        ticketsWithCustomLabels++;\n    }\n}\n\n// --- Build Markdown output ---\nlet fullMarkdownContent = `## 📊 Ticket Summary\\n`;\nfullMarkdownContent += `List of customer-facing improvements and bug fixes.\\n`;\nfullMarkdownContent += `- Release labels: **${versionsLabels.join(', ')}** ${dateLabels}\\n`;\nfullMarkdownContent += `- Total tickets: **${totalTickets}**\\n`;\nfullMarkdownContent += `- Tickets with Zendesk: **${ticketsWithZendesk}**\\n`;\nfullMarkdownContent += `- Tickets with custom labels (${includeLabels.join(', ')}): **${ticketsWithCustomLabels}**\\n`;\n\nfullMarkdownContent += `\\n## 🗂️ Tickets\\n`;\n\nconst sortedTeams = Object.keys(issuesByTeam).sort();\nfor (const team of sortedTeams) {\n    const teamIssues = issuesByTeam[team];\n    teamIssues.sort((a, b) => (a.identifier && b.identifier) ? a.identifier.localeCompare(b.identifier) : 0);\n\n    fullMarkdownContent += `\\n### ${team}\\n`;\n\n    for (const issue of teamIssues) {\n        const labels = (issue.labels?.nodes || []).map(l => l.name).join(\", \");\n\n        const linearLink = APPEND_LINKS\n            ? `[${issue.identifier}](https://linear.app/YOUR_WORKSPACE/issue/${issue.identifier})`\n            : issue.identifier;\n\n        // --- Zendesk link (per issue) ---\n        let zendeskLink = \"\";\n        const zAttachment = (issue.attachments?.nodes || []).find(att => att.url?.includes(\"your-company.zendesk.com\"));\n        if (zAttachment) {\n            const ticketMatch = zAttachment.url.match(/tickets\\/(\\d+)/);\n            const ticketId = ticketMatch?.[1];\n            if (ticketId) {\n                zendeskLink = APPEND_LINKS\n                    ? `(Zendesk [#${ticketId}](${zAttachment.url}))`\n                    : `(Zendesk #${ticketId})`;\n            }\n        }\n\n        // --- Slack link ---\n        const slackLinkUrl = extractFirstSlackLink(issue.attachments);\n        const slackLink = slackLinkUrl\n            ? APPEND_LINKS\n                ? `(Slack [link](${slackLinkUrl}))`\n                : `(Slack link)`\n            : \"\";\n\n        fullMarkdownContent += `- [ ] ${linearLink} - ${issue.title} - ${labels} ${zendeskLink} ${slackLink}`.trim() + `\\n`;\n    }\n}\n\nreturn [{ json: { markdown: fullMarkdownContent } }];\n"},"executeOnce":true,"typeVersion":2},{"id":"8e461938-7d33-4032-a94d-9cb446aabe9b","name":"Post Summary to GitLab MR","type":"n8n-nodes-base.httpRequest","position":[4112,-352],"parameters":{"url":"=https://gitlab.example.com/api/v4/projects/YOUR_PROJECT_ID/merge_requests/{{ $('When GitLab MR Event Occurs').first().json.body.object_attributes.iid }}/discussions","method":"POST","options":{},"sendBody":true,"contentType":"form-urlencoded","authentication":"predefinedCredentialType","bodyParameters":{"parameters":[{"name":"body","value":"={{ $('Generate Summary with Links').item.json.markdown }}"}]},"nodeCredentialType":"gitlabApi"},"typeVersion":4.2},{"id":"48439997-26ab-4768-b0d9-c3bd82697bf6","name":"Check Release Label","type":"n8n-nodes-base.if","position":[2032,-16],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"8f26658c-815b-4dd9-8aef-2a5898933c43","operator":{"type":"boolean","operation":"true","singleValue":true},"leftValue":"={{ $json.body.object_attributes.labels.some(label => label.title === \"rn-release-n8n\") }}","rightValue":"rn-release-n8n"}]}},"typeVersion":2.2},{"id":"88428b83-0ee2-45af-92fa-efc356ef3855","name":"Post Linear Issues to GraphQL","type":"n8n-nodes-base.httpRequest","position":[3376,-16],"parameters":{"url":"https://api.linear.app/graphql","method":"POST","options":{},"jsonBody":"={\n  \"query\": \"query ($labels: [String!], $after: String) { issues(filter: { state: { type: { eq: \\\"completed\\\" }}, or: [ { attachments: { url: { contains: \\\"zendesk.com\\\" }}}, { attachments: { url: { contains: \\\"your-company.slack.com/archives/\\\" }}} {{ $('Parse Linear Version Labels').first().json.custom_labels_expanded.filter(label => label).map(label => `, { labels: { some: { name: { eq: \\\\\"${label}\\\\\" } } } }`).join('') }} ], labels: { some: { name: { in: $labels } } } }, first: 250, after: $after ) { nodes { id identifier title description state { name } createdAt labels { nodes { name } } project { id name progress } team { name } url assignee { name } attachments { nodes { url } } } pageInfo { hasNextPage endCursor } }}\",\n  \"variables\": {\n    \"labels\": {{ JSON.stringify($json.versions_labels_expanded) }}\n    {{ $json[\"endCursor\"] ? `,\"after\":\"${$json[\"endCursor\"]}\"` : \"\" }}\n  }\n}","sendBody":true,"specifyBody":"json","authentication":"predefinedCredentialType","nodeCredentialType":"linearApi"},"typeVersion":4.2},{"id":"a82e7680-3096-4233-8ff2-6dff73c7f9e7","name":"Read RSS Feed","type":"n8n-nodes-base.rssFeedRead","position":[2256,-16],"parameters":{"url":"https://YOUR_DOCS_SITE.com/releases/saas/rss.xml","options":{}},"typeVersion":1.1},{"id":"b8c15737-f189-4f31-a9dd-09566b6c2bc6","name":"Filter Labels Starting With v2","type":"n8n-nodes-base.code","position":[2704,-16],"parameters":{"jsCode":"// Extract and filter labels starting with \"v2\"\nconst nodes = items[0].json.data.issueLabels.nodes;\n\nconst versionLabels = nodes\n  .filter(node => node.name.startsWith('v'))\n  .map(node => ({\n    label: node.name,\n    createdAt: node.createdAt,\n    number: parseInt(node.name.split('.')[1])\n  }));\n\n// Sort by the numeric part\nversionLabels.sort((a, b) => a.number - b.number);\n\n// Get the first and last labels and their dates\nconst startLabel = versionLabels[0].label;\nconst endLabel = versionLabels[versionLabels.length - 1].label;\nconst oldestLabelDate = versionLabels[0].createdAt;\nconst newestLabelDate = versionLabels[versionLabels.length - 1].createdAt;\n\n// Format ISO date to \"YYYY-MM-DD\"\nconst formatDate = (isoString) => isoString.split('T')[0];\n\nconst formattedOldestLabelDate = formatDate(oldestLabelDate);\nconst formattedNewestLabelDate = formatDate(newestLabelDate);\n\n// Build final range string like \"v2.231-v2.233\"\nconst versionRange = `${startLabel}-${endLabel}`;\n\n// Return in proper structure\nreturn [\n  {\n    json: {\n      versionRange,\n      oldestLabelDate: formattedOldestLabelDate,\n      newestLabelDate: formattedNewestLabelDate,\n    }\n  }\n];"},"typeVersion":2},{"id":"8ac651c7-6395-424c-94b2-ed3110df0ac4","name":"Update GitLab MR Label","type":"n8n-nodes-base.httpRequest","position":[4848,80],"parameters":{"url":"=https://gitlab.example.com/api/v4/projects/YOUR_PROJECT_ID/merge_requests/{{ $('When GitLab MR Event Occurs').first().json.body.object_attributes.iid }}","method":"PUT","options":{},"sendBody":true,"contentType":"form-urlencoded","authentication":"predefinedCredentialType","bodyParameters":{"parameters":[{"name":"labels","value":"=rn-done{{ $json.labels.slice(0, 4).filter(l => l && !['rn-release-n8n', 'rn-self-hosted-n8n'].includes(l)).map(l => ',' + l).join('') }}"}]},"nodeCredentialType":"gitlabApi"},"typeVersion":4.2},{"id":"15eb556c-8bb4-4df3-871a-b991bfd41eb2","name":"Check MR for RN Inputs","type":"n8n-nodes-base.if","position":[1808,-16],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"8f26658c-815b-4dd9-8aef-2a5898933c43","operator":{"type":"boolean","operation":"false","singleValue":true},"leftValue":"={{ $json.body.object_attributes.labels.some(label => label.title === \"rn-done\") }}","rightValue":""}]}},"typeVersion":2.2},{"id":"d9e88c8c-4e77-411a-8546-5ba33b330d43","name":"Check Linear Issues Exist","type":"n8n-nodes-base.if","position":[3600,-16],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"524f5209-4d6e-4c8d-99ac-4a6912a0db7b","operator":{"type":"number","operation":"notEquals"},"leftValue":"={{ $json[\"data\"][\"issues\"][\"nodes\"].length }}","rightValue":0}]}},"typeVersion":2.2},{"id":"93e177c7-5c89-448e-bba1-c3d1e3092bf3","name":"Post No Issues Note to GitLab","type":"n8n-nodes-base.httpRequest","position":[4624,176],"parameters":{"url":"=https://gitlab.example.com/api/v4/projects/YOUR_PROJECT_ID/merge_requests/{{ $('When GitLab MR Event Occurs').first().json.body.object_attributes.iid }}/notes","method":"POST","options":{},"sendBody":true,"contentType":"form-urlencoded","authentication":"predefinedCredentialType","bodyParameters":{"parameters":[{"name":"body","value":"=There are no recent Linear issues with Zendesk tickets attached for ``{{ $('Parse Linear Version Labels').item.json.versionRange }}`` or with the following labels: ``{{ $('Parse Linear Version Labels').item.json.custom_labels }}``."}]},"nodeCredentialType":"gitlabApi"},"typeVersion":4.2},{"id":"43dee234-9f01-4304-a417-40233945fb2c","name":"Generate Summary with Linear Details","type":"n8n-nodes-base.code","position":[3824,-16],"parameters":{"jsCode":"const APPEND_LINKS = true;\n\n// --- Get label dates ---\nlet dateLabels = \"\";\ntry {\n    const formatLabels = $('Filter Labels Starting With v2')?.first()?.json;\n    const oldestDate = formatLabels?.oldestLabelDate;\n    const newestDate = formatLabels?.newestLabelDate;\n    dateLabels = (oldestDate && newestDate) ? `[${oldestDate} → ${newestDate}]` : \"\";\n} catch (error) {\n    dateLabels = \"\";\n}\n\n// --- Get version labels ---\nlet versionsLabels = [];\ntry {\n    const versionsLabelNodeOutput = $('Parse Linear Version Labels').first().json.versions_labels;\n    versionsLabels = typeof versionsLabelNodeOutput === 'string'\n        ? JSON.parse(versionsLabelNodeOutput)\n        : Array.isArray(versionsLabelNodeOutput)\n            ? versionsLabelNodeOutput\n            : [];\n} catch (error) {\n    versionsLabels = [];\n}\n\n// --- Get custom labels to include ---\nlet includeLabels = [];\ntry {\n    const includeLabelNodeOutput = $('Parse Linear Version Labels').first().json.custom_labels;\n    includeLabels = typeof includeLabelNodeOutput === 'string'\n        ? JSON.parse(includeLabelNodeOutput)\n        : Array.isArray(includeLabelNodeOutput)\n            ? includeLabelNodeOutput\n            : [];\n} catch (error) {\n    includeLabels = [];\n}\n\n// --- Initialize stats and grouping ---\nlet totalTickets = 0;\nlet ticketsWithZendesk = 0;\nlet ticketsWithCustomLabels = 0;\nconst issuesByTeam = {};\n\n// --- Slack link helper ---\nfunction extractFirstSlackLink(attachments) {\n    if (!attachments || !Array.isArray(attachments.nodes)) return null;\n    for (const att of attachments.nodes) {\n        if (att.url?.includes(\"your-company.slack.com/archives/\")) {\n            return att.url;\n        }\n    }\n    return null;\n}\n\n// --- Process each issue ---\nconst data = $input.first().json.data; // Get the original data\nconst items = data.issues.nodes;\nfor (const item of items) {\n    const issue = item;\n    totalTickets++;\n\n    const teamName = issue.team?.name || 'Unknown Team';\n    if (!issuesByTeam[teamName]) {\n        issuesByTeam[teamName] = [];\n    }\n    issuesByTeam[teamName].push(issue);\n\n    const zendeskAttachment = (issue.attachments?.nodes || []).find(att => att.url?.includes(\"your-company.zendesk.com\"));\n    if (zendeskAttachment) {\n        ticketsWithZendesk++;\n    }\n\n    const issueLabels = issue.labels?.nodes || [];\n    if (includeLabels.length > 0 && issueLabels.some(l => includeLabels.includes(l.name))) {\n        ticketsWithCustomLabels++;\n    }\n}\n\n// --- Build Markdown output ---\nlet fullMarkdownContent = `## 📊 Ticket Summary\\n`;\nfullMarkdownContent += `List of customer-facing improvements and bug fixes.\\n`;\nfullMarkdownContent += `- Release labels: **${versionsLabels.join(', ')}** ${dateLabels}\\n`;\nfullMarkdownContent += `- Total tickets: **${totalTickets}**\\n`;\nfullMarkdownContent += `- Tickets with Zendesk: **${ticketsWithZendesk}**\\n`;\nfullMarkdownContent += `- Tickets with custom labels (${includeLabels.join(', ')}): **${ticketsWithCustomLabels}**\\n`;\n\nfullMarkdownContent += `\\n## 🗂️ Tickets\\n`;\n\nconst sortedTeams = Object.keys(issuesByTeam).sort();\nfor (const team of sortedTeams) {\n    const teamIssues = issuesByTeam[team];\n    teamIssues.sort((a, b) => (a.identifier && b.identifier) ? a.identifier.localeCompare(b.identifier) : 0);\n\n    fullMarkdownContent += `\\n### ${team}\\n`;\n\n    for (const issue of teamIssues) {\n        const labels = (issue.labels?.nodes || []).map(l => l.name).join(\", \");\n\n        const linearLink = APPEND_LINKS\n            ? `[${issue.identifier}](https://linear.app/YOUR_WORKSPACE/issue/${issue.identifier})`\n            : issue.identifier;\n\n        // --- Zendesk link (per issue) ---\n        let zendeskLink = \"\";\n        const zAttachment = (issue.attachments?.nodes || []).find(att => att.url?.includes(\"your-company.zendesk.com\"));\n        if (zAttachment) {\n            const ticketMatch = zAttachment.url.match(/tickets\\/(\\d+)/);\n            const ticketId = ticketMatch?.[1];\n            if (ticketId) {\n                zendeskLink = APPEND_LINKS\n                    ? `(Zendesk [#${ticketId}](${zAttachment.url}))`\n                    : `(Zendesk #${ticketId})`;\n            }\n        }\n\n        // --- Slack link ---\n        const slackLinkUrl = extractFirstSlackLink(issue.attachments);\n        const slackLink = slackLinkUrl\n            ? APPEND_LINKS\n                ? `(Slack [link](${slackLinkUrl}))`\n                : `(Slack link)`\n            : \"\";\n\n        fullMarkdownContent += `- ${linearLink} - ${issue.title} - ${labels} ${zendeskLink} ${slackLink}`.trim() + `\\n`;\n        fullMarkdownContent += `<description>${issue.description}</description>\\n\\n`;\n    }\n}\n\nreturn [{ json: { markdown: fullMarkdownContent } }];\n"},"executeOnce":true,"typeVersion":2},{"id":"62ba83df-7062-4e52-9ef7-58494ff1e129","name":"Post AI Suggestions to GitLab","type":"n8n-nodes-base.httpRequest","position":[4400,-16],"parameters":{"url":"=https://gitlab.example.com/api/v4/projects/YOUR_PROJECT_ID/merge_requests/{{ $('When GitLab MR Event Occurs').first().json.body.object_attributes.iid }}/discussions/{{ $('Post Summary to GitLab MR').first().json.id}}/notes","method":"POST","options":{},"sendBody":true,"contentType":"form-urlencoded","authentication":"predefinedCredentialType","bodyParameters":{"parameters":[{"name":"body","value":"=## 🤖 Suggestion from Claude AI ✨ to be verified:\n\n{{ $json.content[0].text }}\n\n\n\n\n"}]},"nodeCredentialType":"gitlabApi"},"typeVersion":4.2},{"id":"9f924b06-9a51-41c2-a0e4-ccc7a7039d9e","name":"When GitLab MR Event Occurs","type":"n8n-nodes-base.gitlabTrigger","position":[1584,-16],"webhookId":"38618212-5765-47bc-bb3d-e93b8d0ad8f4","parameters":{"owner":"your-org","events":["merge_requests"],"repository":"your-docs-repo"},"typeVersion":1},{"id":"07e3e3b1-1b2a-4a23-a767-959de9c8fbfe","name":"Claude AI Message","type":"@n8n/n8n-nodes-langchain.anthropic","position":[4048,-16],"parameters":{"modelId":{"__rl":true,"mode":"list","value":"claude-opus-4-6","cachedResultName":"claude-opus-4-6"},"options":{},"messages":{"values":[{"content":"=You are a technical writer generating concise, customer-facing release notes.\n\n<data>\n{{ $json.markdown }}\n</data>\n\n<task>\nFor each issue in the data above, write a single-line release note entry suitable for a public changelog.\n</task>\n\n<formatting_instructions>\n- Group entries into two sections:\n  - `### Enhancements` — new features, improvements, or UX changes\n  - `### Fixes` — bugs, regressions, or reliability issues\n- Each entry must:\n  - Start with `- [ ] `\n  - Begin with the **Feature Area** in bold (e.g. `**API**:`, `**Dashboard**:`)\n  - Be written in clear, customer-friendly language — no internal jargon\n  - End with the linked Linear issue ID: `**[ISSUE-123](https://linear.app/YOUR_WORKSPACE/issue/ISSUE-123)**`\n- Group multiple entries under the same Feature Area using nested bullets\n- Do not include emojis\n- Use a professional tone appropriate for an external changelog\n</formatting_instructions>\n\n<example>\n### Enhancements\n- [ ] **API**: Added support for filtering by key/value pairs, improving search flexibility. **[ISSUE-101](https://linear.app/YOUR_WORKSPACE/issue/ISSUE-101)**\n- [ ] **Dashboard**:\n  - [ ] Introduced a new summary view for faster navigation. **[ISSUE-102](https://linear.app/YOUR_WORKSPACE/issue/ISSUE-102)**\n  - [ ] Export to CSV now includes all custom fields. **[ISSUE-103](https://linear.app/YOUR_WORKSPACE/issue/ISSUE-103)**\n\n### Fixes\n- [ ] **Notifications**: Fixed an issue where alerts were sent to deactivated users. **[ISSUE-104](https://linear.app/YOUR_WORKSPACE/issue/ISSUE-104)**\n</example>\n\n<notes>\nUse the issue title, description, and labels from the data to craft accurate summaries.\nIf the feature area is unclear, infer it from the issue's team or labels.\nIf the title is vague, use the description to understand the user impact.\nKeep each entry concise and focused on what changed for the user.\n</notes>"}]}},"typeVersion":1},{"id":"67c47700-2691-4c95-a9fc-c6b452c9ba3c","name":"Fetch GitLab MR Labels","type":"n8n-nodes-base.httpRequest","position":[4624,-16],"parameters":{"url":"=https://gitlab.example.com/api/v4/projects/YOUR_PROJECT_ID/merge_requests/{{ $('When GitLab MR Event Occurs').first().json.body.object_attributes.iid }}","options":{},"authentication":"predefinedCredentialType","nodeCredentialType":"gitlabApi"},"typeVersion":4.2},{"id":"5c1e84ba-7f8d-4964-b0f3-26d974f33b5d","name":"Post Linear Labels to GraphQL","type":"n8n-nodes-base.httpRequest","position":[2480,-16],"parameters":{"url":"https://api.linear.app/graphql","method":"POST","options":{},"jsonBody":"={\n  \"query\": \"query ($afterDate: DateTimeOrDuration!, $beforeDate: DateTimeOrDuration!) { issueLabels(filter: { createdAt: { gt: $afterDate, lt: $beforeDate }, name: { startsWith: \\\"v2\\\" } }) { nodes { name, createdAt } } }\",\n  \"variables\": {\n    \"afterDate\": \"{{ $json.isoDate }}\",\n    \"beforeDate\": \"{{ new Date().toISOString().split('T')[0] + 'T23:59:59.999Z' }}\"\n  }\n}","sendBody":true,"specifyBody":"json","authentication":"predefinedCredentialType","nodeCredentialType":"linearApi"},"executeOnce":true,"typeVersion":4.2},{"id":"e4f65727-ef31-452f-ab49-efbacc42a977","name":"Parse Linear Version Labels","type":"n8n-nodes-base.code","position":[3152,-16],"parameters":{"jsCode":"// Parse version_labels\nconst rawVersionLabels = $json['versions_labels'];\nconst versionLabels = typeof rawVersionLabels === 'string' ? JSON.parse(rawVersionLabels) : rawVersionLabels;\n\n// Parse custom_labels\nconst rawCustomLabels = $json['custom_labels'];\nconst customLabels = typeof rawCustomLabels === 'string' ? JSON.parse(rawCustomLabels) : rawCustomLabels;\n\nconst versions_labels_expanded = [];\nconst custom_labels_expanded = []; // NEW: Array to hold expanded custom labels\n\n// Helper function for expanding version ranges\nfunction expandVersionRange(label, targetArray) {\n    const match = /^v2\\.(\\d+)-v2\\.(\\d+)$/.exec(label);\n    if (match) {\n        const start = parseInt(match[1], 10);\n        const end = parseInt(match[2], 10);\n        for (let i = start; i <= end; i++) {\n            targetArray.push(`v2.${i}`);\n        }\n    } else {\n        targetArray.push(label); // If not a range, push the label as is\n    }\n}\n\n// Expand versions_labels\nfor (const label of versionLabels) {\n    expandVersionRange(label, versions_labels_expanded);\n}\n\n// NEW: Expand custom_labels\nfor (const label of customLabels) {\n    expandVersionRange(label, custom_labels_expanded);\n}\n\n// Return both expanded arrays in the output JSON\nreturn [{ json: {\n    ...$json, // Keep original input data\n    versions_labels_expanded, // Expanded version labels\n    custom_labels_expanded    // NEW: Expanded custom labels\n} }];"},"typeVersion":2}],"active":false,"pinData":{},"settings":{"executionOrder":"v1"},"versionId":"4c3f15c7-589c-40bf-859f-ccf4a5e22c62","connections":{"Read RSS Feed":{"main":[[{"node":"Post Linear Labels to GraphQL","type":"main","index":0}]]},"Claude AI Message":{"main":[[{"node":"Post AI Suggestions to GitLab","type":"main","index":0}]]},"Set Various Fields":{"main":[[{"node":"Parse Linear Version Labels","type":"main","index":0}]]},"Check Release Label":{"main":[[{"node":"Read RSS Feed","type":"main","index":0}]]},"Check MR for RN Inputs":{"main":[[{"node":"Check Release Label","type":"main","index":0}]]},"Fetch GitLab MR Labels":{"main":[[{"node":"Update GitLab MR Label","type":"main","index":0}]]},"Check Linear Issues Exist":{"main":[[{"node":"Generate Summary with Links","type":"main","index":0},{"node":"Generate Summary with Linear Details","type":"main","index":0}],[{"node":"Post No Issues Note to GitLab","type":"main","index":0}]]},"Generate Summary with Links":{"main":[[{"node":"Post Summary to GitLab MR","type":"main","index":0}]]},"Parse Linear Version Labels":{"main":[[{"node":"Post Linear Issues to GraphQL","type":"main","index":0}]]},"When GitLab MR Event Occurs":{"main":[[{"node":"Check MR for RN Inputs","type":"main","index":0}]]},"Post AI Suggestions to GitLab":{"main":[[{"node":"Fetch GitLab MR Labels","type":"main","index":0}]]},"Post Linear Issues to GraphQL":{"main":[[{"node":"Check Linear Issues Exist","type":"main","index":0}]]},"Post Linear Labels to GraphQL":{"main":[[{"node":"Filter Labels Starting With v2","type":"main","index":0}]]},"Post No Issues Note to GitLab":{"main":[[{"node":"Update GitLab MR Label","type":"main","index":0}]]},"Filter Labels Starting With v2":{"main":[[{"node":"Set Various Fields","type":"main","index":0}]]},"Generate Summary with Linear Details":{"main":[[{"node":"Claude AI Message","type":"main","index":0}]]}}},"lastUpdatedBy":1,"workflowInfo":{"nodeCount":25,"nodeTypes":{"n8n-nodes-base.if":{"count":3},"n8n-nodes-base.set":{"count":1},"n8n-nodes-base.code":{"count":4},"n8n-nodes-base.stickyNote":{"count":7},"n8n-nodes-base.httpRequest":{"count":7},"n8n-nodes-base.rssFeedRead":{"count":1},"n8n-nodes-base.gitlabTrigger":{"count":1},"@n8n/n8n-nodes-langchain.anthropic":{"count":1}}},"status":"published","readyToDemo":null,"user":{"name":"Romain Jouhannet","username":"rjouhann","bio":"Technical Product Manager with a background in software development. Experienced in launching products, leading teams, and creating API-first solutions. Skilled in SaaS, Self-Hosted products, major public clouds, and automation, with a focus on simplifying complex technical concepts for enterprise clients.","verified":true,"links":["https://www.linkedin.com/in/romainj/"],"avatar":"https://gravatar.com/avatar/d85f4c3b78e574e660ecc7f0ad9ffa5151f963e7a2c3d7cbc80c01c2ff01ce30?r=pg&d=retro&size=200"},"nodes":[{"id":19,"icon":"file:httprequest.svg","name":"n8n-nodes-base.httpRequest","codex":{"data":{"alias":["API","Request","URL","Build","cURL"],"resources":{"generic":[{"url":"https://n8n.io/blog/2021-the-year-to-automate-the-new-you-with-n8n/","icon":"☀️","label":"2021: The Year to Automate the New You with n8n"},{"url":"https://n8n.io/blog/why-business-process-automation-with-n8n-can-change-your-daily-life/","icon":"🧬","label":"Why business process automation with n8n can change your daily life"},{"url":"https://n8n.io/blog/automatically-pulling-and-visualizing-data-with-n8n/","icon":"📈","label":"Automatically pulling and visualizing data with n8n"},{"url":"https://n8n.io/blog/learn-how-to-automatically-cross-post-your-content-with-n8n/","icon":"✍️","label":"Learn how to automatically cross-post your content with n8n"},{"url":"https://n8n.io/blog/automatically-adding-expense-receipts-to-google-sheets-with-telegram-mindee-twilio-and-n8n/","icon":"🧾","label":"Automatically Adding Expense Receipts to Google Sheets with Telegram, Mindee, Twilio, and n8n"},{"url":"https://n8n.io/blog/running-n8n-on-ships-an-interview-with-maranics/","icon":"🛳","label":"Running n8n on ships: An interview with Maranics"},{"url":"https://n8n.io/blog/what-are-apis-how-to-use-them-with-no-code/","icon":" 🪢","label":"What are APIs and how to use them with no code"},{"url":"https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/","icon":"⚡️","label":"5 tasks you can automate with the new Notion API "},{"url":"https://n8n.io/blog/world-poetry-day-workflow/","icon":"📜","label":"Celebrating World Poetry Day with a daily poem in Telegram"},{"url":"https://n8n.io/blog/automate-google-apps-for-productivity/","icon":"💡","label":"15 Google apps you can combine and automate to increase productivity"},{"url":"https://n8n.io/blog/automate-designs-with-bannerbear-and-n8n/","icon":"🎨","label":"Automate Designs with Bannerbear and n8n"},{"url":"https://n8n.io/blog/how-uproc-scraped-a-multi-page-website-with-a-low-code-workflow/","icon":" 🕸️","label":"How uProc scraped a multi-page website with a low-code workflow"},{"url":"https://n8n.io/blog/building-an-expense-tracking-app-in-10-minutes/","icon":"📱","label":"Building an expense tracking app in 10 minutes"},{"url":"https://n8n.io/blog/5-workflow-automations-for-mattermost-that-we-love-at-n8n/","icon":"🤖","label":"5 workflow automations for Mattermost that we love at n8n"},{"url":"https://n8n.io/blog/how-to-use-the-http-request-node-the-swiss-army-knife-for-workflow-automation/","icon":"🧰","label":"How to use the HTTP Request Node - The Swiss Army Knife for Workflow Automation"},{"url":"https://n8n.io/blog/learn-how-to-use-webhooks-with-mattermost-slash-commands/","icon":"🦄","label":"Learn how to use webhooks with Mattermost slash commands"},{"url":"https://n8n.io/blog/how-a-membership-development-manager-automates-his-work-and-investments/","icon":"📈","label":"How a Membership Development Manager automates his work and investments"},{"url":"https://n8n.io/blog/a-low-code-bitcoin-ticker-built-with-questdb-and-n8n-io/","icon":"📈","label":"A low-code bitcoin ticker built with QuestDB and n8n.io"},{"url":"https://n8n.io/blog/how-to-set-up-a-ci-cd-pipeline-with-no-code/","icon":"🎡","label":"How to set up a no-code CI/CD pipeline with GitHub and TravisCI"},{"url":"https://n8n.io/blog/automations-for-activists/","icon":"✨","label":"How Common Knowledge use workflow automation for activism"},{"url":"https://n8n.io/blog/creating-scheduled-text-affirmations-with-n8n/","icon":"🤟","label":"Creating scheduled text affirmations with n8n"},{"url":"https://n8n.io/blog/how-goomer-automated-their-operations-with-over-200-n8n-workflows/","icon":"🛵","label":"How Goomer automated their operations with over 200 n8n workflows"},{"url":"https://n8n.io/blog/aws-workflow-automation/","label":"7 no-code workflow automations for Amazon Web Services"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.httprequest/"}]},"categories":["Development","Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers"]}}},"group":"[\"output\"]","defaults":{"name":"HTTP Request","color":"#0004F5"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHZpZXdCb3g9IjAgMCA0MCA0MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik00MCAyMEM0MCA4Ljk1MzE0IDMxLjA0NjkgMCAyMCAwQzguOTUzMTQgMCAwIDguOTUzMTQgMCAyMEMwIDMxLjA0NjkgOC45NTMxNCA0MCAyMCA0MEMzMS4wNDY5IDQwIDQwIDMxLjA0NjkgNDAgMjBaTTIwIDM2Ljk0NThDMTguODg1MiAzNi45NDU4IDE3LjEzNzggMzUuOTY3IDE1LjQ5OTggMzIuNjk4NUMxNC43OTY0IDMxLjI5MTggMTQuMTk2MSAyOS41NDMxIDEzLjc1MjYgMjcuNjg0N0gyNi4xODk4QzI1LjgwNDUgMjkuNTQwMyAyNS4yMDQ0IDMxLjI5MDEgMjQuNTAwMiAzMi42OTg1QzIyLjg2MjIgMzUuOTY3IDIxLjExNDggMzYuOTQ1OCAyMCAzNi45NDU4Wk0xMi45MDY0IDIwQzEyLjkwNjQgMjEuNjA5NyAxMy4wMDg3IDIzLjE2NCAxMy4yMDAzIDI0LjYzMDVIMjYuNzk5N0MyNi45OTEzIDIzLjE2NCAyNy4wOTM2IDIxLjYwOTcgMjcuMDkzNiAyMEMyNy4wOTM2IDE4LjM5MDMgMjYuOTkxMyAxNi44MzYgMjYuNzk5NyAxNS4zNjk1SDEzLjIwMDNDMTMuMDA4NyAxNi44MzYgMTIuOTA2NCAxOC4zOTAzIDEyLjkwNjQgMjBaTTIwIDMuMDU0MTlDMjEuMTE0OSAzLjA1NDE5IDIyLjg2MjIgNC4wMzA3OCAyNC41MDAxIDcuMzAwMzlDMjUuMjA2NiA4LjcxNDA4IDI1LjgwNzIgMTAuNDA2NyAyNi4xOTIgMTIuMzE1M0gxMy43NTAxQzE0LjE5MzMgMTAuNDA0NyAxNC43OTQyIDguNzEyNTQgMTUuNDk5OCA3LjMwMDY0QzE3LjEzNzcgNC4wMzA4MyAxOC44ODUxIDMuMDU0MTkgMjAgMy4wNTQxOVpNMzAuMTQ3OCAyMEMzMC4xNDc4IDE4LjQwOTkgMzAuMDU0MyAxNi44NjE3IDI5LjgyMjcgMTUuMzY5NUgzNi4zMDQyQzM2LjcyNTIgMTYuODQyIDM2Ljk0NTggMTguMzk2NCAzNi45NDU4IDIwQzM2Ljk0NTggMjEuNjAzNiAzNi43MjUyIDIzLjE1OCAzNi4zMDQyIDI0LjYzMDVIMjkuODIyN0MzMC4wNTQzIDIzLjEzODMgMzAuMTQ3OCAyMS41OTAxIDMwLjE0NzggMjBaTTI2LjI3NjcgNC4yNTUxMkMyNy42MzY1IDYuMzYwMTkgMjguNzExIDkuMTMyIDI5LjM3NzQgMTIuMzE1M0gzNS4xMDQ2QzMzLjI1MTEgOC42NjggMzAuMTA3IDUuNzgzNDYgMjYuMjc2NyA0LjI1NTEyWk0xMC42MjI2IDEyLjMxNTNINC44OTI5M0M2Ljc1MTQ3IDguNjY3ODQgOS44OTM1MSA1Ljc4MzQxIDEzLjcyMzIgNC4yNTUxM0MxMi4zNjM1IDYuMzYwMjEgMTEuMjg5IDkuMTMyMDEgMTAuNjIyNiAxMi4zMTUzWk0zLjA1NDE5IDIwQzMuMDU0MTkgMjEuNjAzIDMuMjc3NDMgMjMuMTU3NSAzLjY5NDg0IDI0LjYzMDVIMTAuMTIxN0M5Ljk0NjE5IDIzLjE0MiA5Ljg1MjIyIDIxLjU5NDMgOS44NTIyMiAyMEM5Ljg1MjIyIDE4LjQwNTcgOS45NDYxOSAxNi44NTggMTAuMTIxNyAxNS4zNjk1SDMuNjk0ODRDMy4yNzc0MyAxNi44NDI1IDMuMDU0MTkgMTguMzk3IDMuMDU0MTkgMjBaTTI2LjI3NjYgMzUuNzQyN0MyNy42MzY1IDMzLjYzOTMgMjguNzExIDMwLjg2OCAyOS4zNzc0IDI3LjY4NDdIMzUuMTA0NkMzMy4yNTEgMzEuMzMyMiAzMC4xMDY4IDM0LjIxNzkgMjYuMjc2NiAzNS43NDI3Wk0xMy43MjM0IDM1Ljc0MjdDOS44OTM2OSAzNC4yMTc5IDYuNzUxNTUgMzEuMzMyNCA0Ljg5MjkzIDI3LjY4NDdIMTAuNjIyNkMxMS4yODkgMzAuODY4IDEyLjM2MzUgMzMuNjM5MyAxMy43MjM0IDM1Ljc0MjdaIiBmaWxsPSIjM0E0MkU5Ii8+Cjwvc3ZnPgo="},"displayName":"HTTP Request","typeVersion":4,"nodeCategories":[{"id":5,"name":"Development"},{"id":9,"name":"Core Nodes"}]},{"id":20,"icon":"fa:map-signs","name":"n8n-nodes-base.if","codex":{"data":{"alias":["Router","Filter","Condition","Logic","Boolean","Branch"],"details":"The IF node can be used to implement binary conditional logic in your workflow. You can set up one-to-many conditions to evaluate each item of data being inputted into the node. That data will either evaluate to TRUE or FALSE and route out of the node accordingly.\n\nThis node has multiple types of conditions: Bool, String, Number, and Date & Time.","resources":{"generic":[{"url":"https://n8n.io/blog/learn-to-automate-your-factorys-incident-reporting-a-step-by-step-guide/","icon":"🏭","label":"Learn to Automate Your Factory's Incident Reporting: A Step by Step Guide"},{"url":"https://n8n.io/blog/2021-the-year-to-automate-the-new-you-with-n8n/","icon":"☀️","label":"2021: The Year to Automate the New You with n8n"},{"url":"https://n8n.io/blog/why-business-process-automation-with-n8n-can-change-your-daily-life/","icon":"🧬","label":"Why business process automation with n8n can change your daily life"},{"url":"https://n8n.io/blog/create-a-toxic-language-detector-for-telegram/","icon":"🤬","label":"Create a toxic language detector for Telegram in 4 step"},{"url":"https://n8n.io/blog/no-code-ecommerce-workflow-automations/","icon":"store","label":"6 e-commerce workflows to power up your Shopify s"},{"url":"https://n8n.io/blog/how-to-build-a-low-code-self-hosted-url-shortener/","icon":"🔗","label":"How to build a low-code, self-hosted URL shortener in 3 steps"},{"url":"https://n8n.io/blog/automate-your-data-processing-pipeline-in-9-steps-with-n8n/","icon":"⚙️","label":"Automate your data processing pipeline in 9 steps"},{"url":"https://n8n.io/blog/how-to-get-started-with-crm-automation-and-no-code-workflow-ideas/","icon":"👥","label":"How to get started with CRM automation (with 3 no-code workflow ideas"},{"url":"https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/","icon":"⚡️","label":"5 tasks you can automate with the new Notion API "},{"url":"https://n8n.io/blog/automate-google-apps-for-productivity/","icon":"💡","label":"15 Google apps you can combine and automate to increase productivity"},{"url":"https://n8n.io/blog/automation-for-maintainers-of-open-source-projects/","icon":"🏷️","label":"How to automatically manage contributions to open-source projects"},{"url":"https://n8n.io/blog/how-uproc-scraped-a-multi-page-website-with-a-low-code-workflow/","icon":" 🕸️","label":"How uProc scraped a multi-page website with a low-code workflow"},{"url":"https://n8n.io/blog/5-workflow-automations-for-mattermost-that-we-love-at-n8n/","icon":"🤖","label":"5 workflow automations for Mattermost that we love at n8n"},{"url":"https://n8n.io/blog/why-this-product-manager-loves-workflow-automation-with-n8n/","icon":"🧠","label":"Why this Product Manager loves workflow automation with n8n"},{"url":"https://n8n.io/blog/sending-automated-congratulations-with-google-sheets-twilio-and-n8n/","icon":"🙌","label":"Sending Automated Congratulations with Google Sheets, Twilio, and n8n "},{"url":"https://n8n.io/blog/how-to-set-up-a-ci-cd-pipeline-with-no-code/","icon":"🎡","label":"How to set up a no-code CI/CD pipeline with GitHub and TravisCI"},{"url":"https://n8n.io/blog/benefits-of-automation-and-n8n-an-interview-with-hubspots-hugh-durkin/","icon":"🎖","label":"Benefits of automation and n8n: An interview with HubSpot's Hugh Durkin"},{"url":"https://n8n.io/blog/aws-workflow-automation/","label":"7 no-code workflow automations for Amazon Web Services"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.if/"}]},"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Flow"]}}},"group":"[\"transform\"]","defaults":{"name":"If","color":"#408000"},"iconData":{"icon":"map-signs","type":"icon"},"displayName":"If","typeVersion":2,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":37,"icon":"fa:rss","name":"n8n-nodes-base.rssFeedRead","codex":{"data":{"resources":{"generic":[{"url":"https://n8n.io/blog/why-business-process-automation-with-n8n-can-change-your-daily-life/","icon":"🧬","label":"Why business process automation with n8n can change your daily life"},{"url":"https://n8n.io/blog/why-i-chose-n8n-over-zapier-in-2020/","icon":"😍","label":"Why I chose n8n over Zapier in 2020"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.rssfeedread/"}]},"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0"}},"group":"[\"input\"]","defaults":{"name":"RSS Read","color":"#b02020"},"iconData":{"icon":"rss","type":"icon"},"displayName":"RSS Read","typeVersion":1,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":38,"icon":"fa:pen","name":"n8n-nodes-base.set","codex":{"data":{"alias":["Set","JS","JSON","Filter","Transform","Map"],"resources":{"generic":[{"url":"https://n8n.io/blog/learn-to-automate-your-factorys-incident-reporting-a-step-by-step-guide/","icon":"🏭","label":"Learn to Automate Your Factory's Incident Reporting: A Step by Step Guide"},{"url":"https://n8n.io/blog/2021-the-year-to-automate-the-new-you-with-n8n/","icon":"☀️","label":"2021: The Year to Automate the New You with n8n"},{"url":"https://n8n.io/blog/automatically-pulling-and-visualizing-data-with-n8n/","icon":"📈","label":"Automatically pulling and visualizing data with n8n"},{"url":"https://n8n.io/blog/database-monitoring-and-alerting-with-n8n/","icon":"📡","label":"Database Monitoring and Alerting with n8n"},{"url":"https://n8n.io/blog/automatically-adding-expense-receipts-to-google-sheets-with-telegram-mindee-twilio-and-n8n/","icon":"🧾","label":"Automatically Adding Expense Receipts to Google Sheets with Telegram, Mindee, Twilio, and n8n"},{"url":"https://n8n.io/blog/no-code-ecommerce-workflow-automations/","icon":"store","label":"6 e-commerce workflows to power up your Shopify s"},{"url":"https://n8n.io/blog/how-to-build-a-low-code-self-hosted-url-shortener/","icon":"🔗","label":"How to build a low-code, self-hosted URL shortener in 3 steps"},{"url":"https://n8n.io/blog/automate-your-data-processing-pipeline-in-9-steps-with-n8n/","icon":"⚙️","label":"Automate your data processing pipeline in 9 steps"},{"url":"https://n8n.io/blog/how-to-get-started-with-crm-automation-and-no-code-workflow-ideas/","icon":"👥","label":"How to get started with CRM automation (with 3 no-code workflow ideas"},{"url":"https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/","icon":"⚡️","label":"5 tasks you can automate with the new Notion API "},{"url":"https://n8n.io/blog/automate-google-apps-for-productivity/","icon":"💡","label":"15 Google apps you can combine and automate to increase productivity"},{"url":"https://n8n.io/blog/how-uproc-scraped-a-multi-page-website-with-a-low-code-workflow/","icon":" 🕸️","label":"How uProc scraped a multi-page website with a low-code workflow"},{"url":"https://n8n.io/blog/building-an-expense-tracking-app-in-10-minutes/","icon":"📱","label":"Building an expense tracking app in 10 minutes"},{"url":"https://n8n.io/blog/the-ultimate-guide-to-automate-your-video-collaboration-with-whereby-mattermost-and-n8n/","icon":"📹","label":"The ultimate guide to automate your video collaboration with Whereby, Mattermost, and n8n"},{"url":"https://n8n.io/blog/5-workflow-automations-for-mattermost-that-we-love-at-n8n/","icon":"🤖","label":"5 workflow automations for Mattermost that we love at n8n"},{"url":"https://n8n.io/blog/learn-to-build-powerful-api-endpoints-using-webhooks/","icon":"🧰","label":"Learn to Build Powerful API Endpoints Using Webhooks"},{"url":"https://n8n.io/blog/how-a-membership-development-manager-automates-his-work-and-investments/","icon":"📈","label":"How a Membership Development Manager automates his work and investments"},{"url":"https://n8n.io/blog/a-low-code-bitcoin-ticker-built-with-questdb-and-n8n-io/","icon":"📈","label":"A low-code bitcoin ticker built with QuestDB and n8n.io"},{"url":"https://n8n.io/blog/how-to-set-up-a-ci-cd-pipeline-with-no-code/","icon":"🎡","label":"How to set up a no-code CI/CD pipeline with GitHub and TravisCI"},{"url":"https://n8n.io/blog/benefits-of-automation-and-n8n-an-interview-with-hubspots-hugh-durkin/","icon":"🎖","label":"Benefits of automation and n8n: An interview with HubSpot's Hugh Durkin"},{"url":"https://n8n.io/blog/how-goomer-automated-their-operations-with-over-200-n8n-workflows/","icon":"🛵","label":"How Goomer automated their operations with over 200 n8n workflows"},{"url":"https://n8n.io/blog/aws-workflow-automation/","label":"7 no-code workflow automations for Amazon Web Services"}],"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.set/"}]},"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Data Transformation"]}}},"group":"[\"input\"]","defaults":{"name":"Edit Fields"},"iconData":{"icon":"pen","type":"icon"},"displayName":"Edit Fields (Set)","typeVersion":3,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":57,"icon":"file:gitlab.svg","name":"n8n-nodes-base.gitlabTrigger","codex":{"data":{"resources":{"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/trigger-nodes/n8n-nodes-base.gitlabtrigger/"}],"credentialDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/credentials/gitlab/"}]},"categories":["Development"],"nodeVersion":"1.0","codexVersion":"1.0"}},"group":"[\"trigger\"]","defaults":{"name":"GitLab Trigger"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MCIgaGVpZ2h0PSI2MCI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGZpbGw9IiNFMjQzMjkiIGQ9Im0zMC41IDU2Ljk2NyAxMC44NjMtMzMuMjAzSDE5LjYzN3oiLz48cGF0aCBmaWxsPSIjRkM2RDI2IiBkPSJNMzAuNSA1Ni45NjcgMTkuNjM3IDIzLjc2NEg0LjQxeiIvPjxwYXRoIGZpbGw9IiNGQ0EzMjYiIGQ9Ik00LjQxIDIzLjc2NCAxLjExIDMzLjg2Yy0uMy45Mi4wMyAxLjkyOC44MTcgMi40OTZMMzAuNSA1Ni45Njd6Ii8+PHBhdGggZmlsbD0iI0UyNDMyOSIgZD0iTTQuNDEgMjMuNzY0aDE1LjIyNkwxMy4wOTMgMy43ODFjLS4zMzYtMS4wMjktMS44MDItMS4wMjktMi4xMzkgMHoiLz48cGF0aCBmaWxsPSIjRkM2RDI2IiBkPSJtMzAuNSA1Ni45NjcgMTAuODYzLTMzLjIwM0g1Ni41OXoiLz48cGF0aCBmaWxsPSIjRkNBMzI2IiBkPSJtNTYuNTkgMjMuNzY0IDMuMyAxMC4wODZhMi4yMiAyLjIyIDAgMCAxLS44MTcgMi40OTdMMzAuNSA1Ni45Njd6Ii8+PHBhdGggZmlsbD0iI0UyNDMyOSIgZD0iTTU2LjU5IDIzLjc2NEg0MS4zNjJsNi41NDQtMTkuOTkyYy4zMzYtMS4wMyAxLjgwMi0xLjAzIDIuMTM5IDBsNi41NDMgMTkuOTkyeiIvPjwvZz48L3N2Zz4="},"displayName":"GitLab Trigger","typeVersion":1,"nodeCategories":[{"id":5,"name":"Development"}]},{"id":565,"icon":"fa:sticky-note","name":"n8n-nodes-base.stickyNote","codex":{"data":{"alias":["Comments","Notes","Sticky"],"categories":["Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers"]}}},"group":"[\"input\"]","defaults":{"name":"Sticky Note","color":"#FFD233"},"iconData":{"icon":"sticky-note","type":"icon"},"displayName":"Sticky Note","typeVersion":1,"nodeCategories":[{"id":9,"name":"Core Nodes"}]},{"id":834,"icon":"file:code.svg","name":"n8n-nodes-base.code","codex":{"data":{"alias":["cpde","Javascript","JS","Python","Script","Custom Code","Function"],"details":"The Code node allows you to execute JavaScript in your workflow.","resources":{"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.code/"}]},"categories":["Development","Core Nodes"],"nodeVersion":"1.0","codexVersion":"1.0","subcategories":{"Core Nodes":["Helpers","Data Transformation"]}}},"group":"[\"transform\"]","defaults":{"name":"Code"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDUxMiA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF8xMTcxXzQ0MSkiPgo8cGF0aCBkPSJNMTcwLjI4MyA0OEgxOTYuNUMyMDMuMTI3IDQ4IDIwOC41IDQyLjYyNzQgMjA4LjUgMzZWMTJDMjA4LjUgNS4zNzI1OCAyMDMuMTI3IDAgMTk2LjUgMEgxNzAuMjgzQzEyNi4xIDAgOTAuMjgzIDM1LjgxNzIgOTAuMjgzIDgwVjE3NkM5MC4yODMgMjA2LjkyOCA2NS4yMTA5IDIzMiAzNC4yODMgMjMySDIzQzE2LjM3MjYgMjMyIDExIDIzNy4zNzIgMTEgMjQ0VjI2OEMxMSAyNzQuNjI3IDE2LjM3MjQgMjgwIDIyLjk5OTYgMjgwTDM0LjI4MyAyODBDNjUuMjEwOSAyODAgOTAuMjgzIDMwNS4wNzIgOTAuMjgzIDMzNlY0NDBDOTAuMjgzIDQ3OS43NjQgMTIyLjUxOCA1MTIgMTYyLjI4MyA1MTJIMTk2LjVDMjAzLjEyNyA1MTIgMjA4LjUgNTA2LjYyNyAyMDguNSA1MDBWNDc2QzIwOC41IDQ2OS4zNzMgMjAzLjEyNyA0NjQgMTk2LjUgNDY0SDE2Mi4yODNDMTQ5LjAyOCA0NjQgMTM4LjI4MyA0NTMuMjU1IDEzOC4yODMgNDQwVjMzNkMxMzguMjgzIDMwOS4wMjIgMTI4LjAxMSAyODQuNDQzIDExMS4xNjQgMjY1Ljk2MUMxMDYuMTA5IDI2MC40MTYgMTA2LjEwOSAyNTEuNTg0IDExMS4xNjQgMjQ2LjAzOUMxMjguMDExIDIyNy41NTcgMTM4LjI4MyAyMDIuOTc4IDEzOC4yODMgMTc2VjgwQzEzOC4yODMgNjIuMzI2OSAxNTIuNjEgNDggMTcwLjI4MyA0OFoiIGZpbGw9IiNGRjk5MjIiLz4KPHBhdGggZD0iTTMwNSAzNkMzMDUgNDIuNjI3NCAzMTAuMzczIDQ4IDMxNyA0OEgzNDIuOTc5QzM2MC42NTIgNDggMzc0Ljk3OCA2Mi4zMjY5IDM3NC45NzggODBWMTc2QzM3NC45NzggMjAyLjk3OCAzODUuMjUxIDIyNy41NTcgNDAyLjA5OCAyNDYuMDM5QzQwNy4xNTMgMjUxLjU4NCA0MDcuMTUzIDI2MC40MTYgNDAyLjA5OCAyNjUuOTYxQzM4NS4yNTEgMjg0LjQ0MyAzNzQuOTc4IDMwOS4wMjIgMzc0Ljk3OCAzMzZWNDMyQzM3NC45NzggNDQ5LjY3MyAzNjAuNjUyIDQ2NCAzNDIuOTc5IDQ2NEgzMTdDMzEwLjM3MyA0NjQgMzA1IDQ2OS4zNzMgMzA1IDQ3NlY1MDBDMzA1IDUwNi42MjcgMzEwLjM3MyA1MTIgMzE3IDUxMkgzNDIuOTc5QzM4Ny4xNjEgNTEyIDQyMi45NzggNDc2LjE4MyA0MjIuOTc4IDQzMlYzMzZDNDIyLjk3OCAzMDUuMDcyIDQ0OC4wNTEgMjgwIDQ3OC45NzkgMjgwSDQ5MEM0OTYuNjI3IDI4MCA1MDIgMjc0LjYyOCA1MDIgMjY4VjI0NEM1MDIgMjM3LjM3MyA0OTYuNjI4IDIzMiA0OTAgMjMyTDQ3OC45NzkgMjMyQzQ0OC4wNTEgMjMyIDQyMi45NzggMjA2LjkyOCA0MjIuOTc4IDE3NlY4MEM0MjIuOTc4IDM1LjgxNzIgMzg3LjE2MSAwIDM0Mi45NzkgMEgzMTdDMzEwLjM3MyAwIDMwNSA1LjM3MjU4IDMwNSAxMlYzNloiIGZpbGw9IiNGRjk5MjIiLz4KPC9nPgo8ZGVmcz4KPGNsaXBQYXRoIGlkPSJjbGlwMF8xMTcxXzQ0MSI+CjxyZWN0IHdpZHRoPSI1MTIiIGhlaWdodD0iNTEyIiBmaWxsPSJ3aGl0ZSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo="},"displayName":"Code","typeVersion":2,"nodeCategories":[{"id":5,"name":"Development"},{"id":9,"name":"Core Nodes"}]},{"id":1312,"icon":"file:anthropic.svg","name":"@n8n/n8n-nodes-langchain.anthropic","codex":{"data":{"alias":["LangChain","document","image","assistant","claude"],"resources":{"primaryDocumentation":[{"url":"https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-langchain.anthropic/"}]},"categories":["AI","Langchain"],"subcategories":{"AI":["Agents","Miscellaneous","Root Nodes"]}}},"group":"[\"transform\"]","defaults":{"name":"Anthropic"},"iconData":{"type":"file","fileBuffer":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0NiIgaGVpZ2h0PSIzMiIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzdEN0Q4NyIgZD0iTTMyLjczIDBoLTYuOTQ1TDM4LjQ1IDMyaDYuOTQ1ek0xMi42NjUgMCAwIDMyaDcuMDgybDIuNTktNi43MmgxMy4yNWwyLjU5IDYuNzJoNy4wODJMMTkuOTI5IDB6bS0uNzAyIDE5LjMzNyA0LjMzNC0xMS4yNDYgNC4zMzQgMTEuMjQ2eiIvPjwvc3ZnPgo="},"displayName":"Anthropic","typeVersion":1,"nodeCategories":[{"id":25,"name":"AI"},{"id":26,"name":"Langchain"}]}],"categories":[{"id":31,"name":"Content Creation"},{"id":49,"name":"AI Summarization"}],"image":[]}}