{
  "id": "06REJtR4u554I4dR",
  "name": "Signal Router",
  "tags": [],
  "nodes": [
    {
      "id": "3006021a-6014-47d4-8fa1-1c22dbe79a52",
      "name": "Sticky Note - How It Works",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1408,
        -48
      ],
      "parameters": {
        "width": 420,
        "height": 440,
        "content": "## How it works\n\nWhen you set a signal's Route Status to 'Routing' in Notion, this workflow picks it up and sends it to the right destination: Jira bug, Jira feature, RICE+ backlog, customer health, or sprint backlog."
      },
      "typeVersion": 1
    },
    {
      "id": "f6aaa487-af0a-4a0e-a30a-d0db7dc630d7",
      "name": "Sticky Note - Routing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -944,
        -64
      ],
      "parameters": {
        "color": 7,
        "width": 1924,
        "height": 1736,
        "content": "## Routing Engine\n\nReads signal details from Notion, flattens properties, then routes via a 5-way switch based on the Route Destination field you selected."
      },
      "typeVersion": 1
    },
    {
      "id": "331189d2-7230-43cb-a190-9be31d459c41",
      "name": "Signal Route Trigger",
      "type": "n8n-nodes-base.notion",
      "polling": true,
      "position": [
        -800,
        704
      ],
      "parameters": {
        "limit": 10,
        "simple": false,
        "options": {},
        "resource": "databasePage",
        "operation": "getAll",
        "databaseId": "31e06bab-3ebe-811b-b204-c5f41b273303",
        "filterType": "singleCondition"
      },
      "typeVersion": 2.2
    },
    {
      "id": "9d7e2e06-8dda-49fd-ac66-9a6b747a52de",
      "name": "Filter Routing Status",
      "type": "n8n-nodes-base.if",
      "position": [
        -496,
        704
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "condition-routing-check",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.properties?.['Route Status']?.select?.name }}",
              "rightValue": "Routing"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "6a7f26bd-15e4-4693-a3f2-2944c78497a3",
      "name": "Read Signal Details",
      "type": "n8n-nodes-base.code",
      "position": [
        -208,
        704
      ],
      "parameters": {
        "jsCode": "// Extract all useful properties from the Notion page into a flat object\nconst props = $json.properties;\n\nconst getValue = (prop) => {\n  if (!prop) return '';\n  if (prop.title) return prop.title.map(t => t.plain_text).join('');\n  if (prop.rich_text) return prop.rich_text.map(t => t.plain_text).join('');\n  if (prop.select) return prop.select?.name || '';\n  if (prop.url) return prop.url || '';\n  if (prop.date) return prop.date?.start || '';\n  return '';\n};\n\nreturn [{\n  json: {\n    notionPageId: $json.id,\n    signal: getValue(props['Signal']),\n    dateCaptured: getValue(props['Date Captured']),\n    sourceChannel: getValue(props['Source Channel']),\n    originalMessage: getValue(props['Original Message']),\n    author: getValue(props['Author']),\n    signalType: getValue(props['Signal Type']),\n    sentiment: getValue(props['Sentiment']),\n    urgency: getValue(props['Urgency']),\n    aiAnalysis: getValue(props['AI Analysis']),\n    customerMentioned: getValue(props['Customer Mentioned']),\n    routeDestination: getValue(props['Route Destination']),\n    routeStatus: getValue(props['Route Status']),\n    threadUrl: getValue(props['Thread URL']),\n    slackMessageId: getValue(props['Slack Message ID'])\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "1a2e166a-b874-441b-af72-a517fbaf89e4",
      "name": "Route Destination",
      "type": "n8n-nodes-base.switch",
      "position": [
        112,
        704
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.routeDestination }}",
                    "rightValue": "Jira Bug"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.routeDestination }}",
                    "rightValue": "Jira Feature"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.routeDestination }}",
                    "rightValue": "RICE+ Backlog"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.routeDestination }}",
                    "rightValue": "Customer Health"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.routeDestination }}",
                    "rightValue": "Sprint Backlog"
                  }
                ]
              }
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.2
    },
    {
      "id": "6bbe4bff-e55e-48d6-8f91-c97fd2f6c082",
      "name": "Create Jira Bug",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        400,
        304
      ],
      "parameters": {
        "url": "https://company.atlassian.net/rest/api/3/issue",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({ fields: { project: { key: 'PROJ' }, issuetype: { name: 'Bug' }, summary: '[Signal] ' + $json.signal, description: { type: 'doc', version: 1, content: [{ type: 'heading', attrs: { level: 3 }, content: [{ type: 'text', text: 'Signal Captured from Slack' }] }, { type: 'paragraph', content: [{ type: 'text', text: 'Source: ', marks: [{ type: 'strong' }] }, { type: 'text', text: $json.sourceChannel + ' by ' + $json.author }] }, { type: 'heading', attrs: { level: 4 }, content: [{ type: 'text', text: 'Original Message' }] }, { type: 'blockquote', content: [{ type: 'paragraph', content: [{ type: 'text', text: $json.originalMessage || '' }] }] }, { type: 'heading', attrs: { level: 4 }, content: [{ type: 'text', text: 'AI Analysis' }] }, { type: 'paragraph', content: [{ type: 'text', text: $json.aiAnalysis || '' }] }, { type: 'paragraph', content: [{ type: 'text', text: 'Slack Thread: ' }, { type: 'text', text: 'View original', marks: [{ type: 'link', attrs: { href: $json.threadUrl || '#' } }] }] }] }, priority: { name: $json.urgency === 'Critical' ? 'Highest' : $json.urgency === 'High' ? 'High' : $json.urgency === 'Medium' ? 'Medium' : 'Low' }, labels: ['signal-catcher', 'slack-signal'] } }) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBasicAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "be4d49da-3b63-42b5-8c29-13f50e1afa24",
      "name": "Parse Jira Bug Response",
      "type": "n8n-nodes-base.code",
      "position": [
        704,
        304
      ],
      "parameters": {
        "jsCode": "// Extract Jira bug ticket reference\nconst jiraKey = $json.key || 'PROJ-???';\nconst signalData = $('Read Signal Details').first().json;\n\nreturn [{\n  json: {\n    ...signalData,\n    routedReference: jiraKey,\n    routedUrl: `https://company.atlassian.net/browse/${jiraKey}`\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "a98d8368-06d9-4cff-9f9f-a527e64cf1c4",
      "name": "Create Jira Feature",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        400,
        512
      ],
      "parameters": {
        "url": "https://company.atlassian.net/rest/api/3/issue",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({ fields: { project: { key: 'PROJ' }, issuetype: { name: 'Story' }, summary: '[Signal] ' + $json.signal, description: { type: 'doc', version: 1, content: [{ type: 'heading', attrs: { level: 3 }, content: [{ type: 'text', text: 'Feature Signal from Slack' }] }, { type: 'paragraph', content: [{ type: 'text', text: 'Source: ', marks: [{ type: 'strong' }] }, { type: 'text', text: $json.sourceChannel + ' by ' + $json.author }] }, { type: 'paragraph', content: [{ type: 'text', text: 'Customer: ', marks: [{ type: 'strong' }] }, { type: 'text', text: $json.customerMentioned || 'None mentioned' }] }, { type: 'heading', attrs: { level: 4 }, content: [{ type: 'text', text: 'Original Message' }] }, { type: 'blockquote', content: [{ type: 'paragraph', content: [{ type: 'text', text: $json.originalMessage || '' }] }] }, { type: 'heading', attrs: { level: 4 }, content: [{ type: 'text', text: 'AI Analysis' }] }, { type: 'paragraph', content: [{ type: 'text', text: $json.aiAnalysis || '' }] }, { type: 'paragraph', content: [{ type: 'text', text: 'Slack Thread: ' }, { type: 'text', text: 'View original', marks: [{ type: 'link', attrs: { href: $json.threadUrl || '#' } }] }] }] }, priority: { name: $json.urgency === 'Critical' ? 'Highest' : $json.urgency === 'High' ? 'High' : 'Medium' }, labels: ['signal-catcher', 'feature-signal', 'slack-signal'] } }) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBasicAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "b736cf5c-d89e-4496-953c-b1ad5a836e33",
      "name": "Parse Jira Feature Response",
      "type": "n8n-nodes-base.code",
      "position": [
        704,
        512
      ],
      "parameters": {
        "jsCode": "// Extract Jira feature ticket reference\nconst jiraKey = $json.key || 'PROJ-???';\nconst signalData = $('Read Signal Details').first().json;\n\nreturn [{\n  json: {\n    ...signalData,\n    routedReference: jiraKey,\n    routedUrl: `https://company.atlassian.net/browse/${jiraKey}`\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "68977746-0f16-4571-8c10-2d698e1a8121",
      "name": "Create RICE+ Entry",
      "type": "n8n-nodes-base.notion",
      "position": [
        400,
        704
      ],
      "parameters": {
        "title": "={{ '[Signal] ' + $json.signal }}",
        "options": {},
        "resource": "databasePage",
        "databaseId": "31e06bab-3ebe-81fe-9d0c-e65a49a99ef3",
        "propertiesUi": {
          "propertyValues": [
            {
              "key": "Source|select",
              "selectValue": "Signal Catcher"
            },
            {
              "key": "Status|select",
              "selectValue": "Proposed"
            },
            {
              "key": "Customer Request|rich_text"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "27c0937e-2eae-4087-98d0-3c37ea268b02",
      "name": "Parse RICE Response",
      "type": "n8n-nodes-base.code",
      "position": [
        704,
        704
      ],
      "parameters": {
        "jsCode": "// Generate a readable reference for the RICE+ entry\nconst riceRef = 'RICE-' + String(Math.floor(Math.random() * 900) + 100).padStart(3, '0');\nconst signalData = $('Read Signal Details').first().json;\n\nreturn [{\n  json: {\n    ...signalData,\n    routedReference: riceRef,\n    routedUrl: $json.url || '#',\n    notionRicePageId: $json.id || ''\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "d6e5ff17-b892-428e-bd15-43161c1ddbc0",
      "name": "Create Customer Health Entry",
      "type": "n8n-nodes-base.notion",
      "position": [
        400,
        912
      ],
      "parameters": {
        "title": "={{ '[Signal] ' + $json.signal }}",
        "options": {},
        "resource": "databasePage",
        "databaseId": "31e06bab-3ebe-811b-b204-c5f41b273303",
        "propertiesUi": {
          "propertyValues": [
            {
              "key": "Account|rich_text"
            },
            {
              "key": "Type|select",
              "selectValue": "={{ $json.signalType === 'Customer Escalation' ? 'Escalation' : $json.signalType === 'Bug Report' ? 'Support Issue' : 'Product Signal' }}"
            },
            {
              "key": "Sentiment|select",
              "selectValue": "={{ $json.sentiment }}"
            },
            {
              "key": "Date|date"
            },
            {
              "key": "Notes|rich_text"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "7463e91e-dded-4608-b358-81e1ddd55578",
      "name": "Parse Customer Health Response",
      "type": "n8n-nodes-base.code",
      "position": [
        704,
        912
      ],
      "parameters": {
        "jsCode": "// Generate a health reference\nconst signalData = $('Read Signal Details').first().json;\nconst healthRef = signalData.customerMentioned\n  ? signalData.customerMentioned.replace(/\\s+/g, '').substring(0, 3).toUpperCase() + '-HEALTH-' + String(Math.floor(Math.random() * 900) + 100).padStart(3, '0')\n  : 'UNK-HEALTH-' + String(Math.floor(Math.random() * 900) + 100).padStart(3, '0');\n\nreturn [{\n  json: {\n    ...signalData,\n    routedReference: healthRef,\n    routedUrl: $json.url || '#',\n    notionHealthPageId: $json.id || ''\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "98a0e380-8810-4d66-aa8b-b36562949507",
      "name": "Create Sprint Backlog Item",
      "type": "n8n-nodes-base.notion",
      "position": [
        400,
        1104
      ],
      "parameters": {
        "title": "={{ '[Signal] ' + $json.signal }}",
        "options": {},
        "resource": "databasePage",
        "databaseId": "31e06bab-3ebe-81fe-9d0c-e65a49a99ef3",
        "propertiesUi": {
          "propertyValues": [
            {
              "key": "Status|select",
              "selectValue": "To Do"
            },
            {
              "key": "Priority|select",
              "selectValue": "={{ $json.urgency }}"
            },
            {
              "key": "Source|select",
              "selectValue": "Signal Catcher"
            },
            {
              "key": "Sprint|select",
              "selectValue": "Sprint 24"
            },
            {
              "key": "Description|rich_text"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "769c9788-87a2-4ce2-8bdf-726fb23dd95d",
      "name": "Parse Sprint Response",
      "type": "n8n-nodes-base.code",
      "position": [
        704,
        1104
      ],
      "parameters": {
        "jsCode": "// Generate a sprint reference\nconst signalData = $('Read Signal Details').first().json;\nconst sprintRef = 'SPRINT-' + String(Math.floor(Math.random() * 900) + 100).padStart(3, '0');\n\nreturn [{\n  json: {\n    ...signalData,\n    routedReference: sprintRef,\n    routedUrl: $json.url || '#',\n    notionSprintPageId: $json.id || ''\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "02da030b-7d20-4ca4-aac7-a53cc9cf1ab0",
      "name": "Update Signal Status",
      "type": "n8n-nodes-base.notion",
      "position": [
        1040,
        704
      ],
      "parameters": {
        "pageId": "={{ $json.notionPageId }}",
        "options": {},
        "resource": "databasePage",
        "operation": "update",
        "propertiesUi": {
          "propertyValues": [
            {
              "key": "Route Status|select",
              "selectValue": "Routed"
            },
            {
              "key": "Routed Reference|rich_text"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "11f1c990-b23d-4a34-8fea-ddee8dbc25bc",
      "name": "Build Thread Reply",
      "type": "n8n-nodes-base.code",
      "position": [
        1312,
        704
      ],
      "parameters": {
        "jsCode": "// Build Slack Thread Reply\nconst signal = $json;\n\n// Determine the channel ID from the name\nconst channelMap = {\n  '#product': 'C01PRODUCT',\n  '#support': 'C02SUPPORT',\n  '#sales': 'C03SALES',\n  '#engineering': 'C04ENGINEERING',\n  '#general': 'C05GENERAL'\n};\n\nconst channelId = channelMap[signal.sourceChannel] || signal.sourceChannel;\n\n// Build confirmation message\nconst destinationLabel = {\n  'Jira Bug': 'Jira Bug Ticket',\n  'Jira Feature': 'Jira Feature Story',\n  'RICE+ Backlog': 'RICE+ Feature Backlog',\n  'Customer Health': 'Customer Health Tracker',\n  'Sprint Backlog': 'Sprint 24 Backlog'\n}[signal.routeDestination] || signal.routeDestination;\n\nconst replyMessage = `Captured and routed to *${signal.routedReference}* (${destinationLabel})`;\n\nreturn [{\n  json: {\n    ...signal,\n    slackChannelId: channelId,\n    replyMessage: replyMessage\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "c802324b-f571-42de-911c-9373ded5060a",
      "name": "Reply in Thread",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1600,
        704
      ],
      "parameters": {
        "url": "https://slack.com/api/chat.postMessage",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({ channel: $json.slackChannelId, text: $json.replyMessage, thread_ts: $json.slackMessageId, unfurl_links: false }) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "slackApi"
      },
      "typeVersion": 4.2,
      "continueOnFail": true
    },
    {
      "id": "d774ff2f-e48c-43dc-9102-1857e7c16929",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1008,
        -64
      ],
      "parameters": {
        "color": 7,
        "width": 800,
        "height": 1744,
        "content": "## Closed Loop\n\nAfter routing, updates the signal status to 'Routed' with a reference link, and replies in the original Slack thread to confirm where it went."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "fa198898-5fb4-4a4d-8f15-4de2d6435afe",
  "connections": {
    "Create Jira Bug": {
      "main": [
        [
          {
            "node": "Parse Jira Bug Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route Destination": {
      "main": [
        [
          {
            "node": "Create Jira Bug",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Create Jira Feature",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Create RICE+ Entry",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Create Customer Health Entry",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Create Sprint Backlog Item",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Thread Reply": {
      "main": [
        [
          {
            "node": "Reply in Thread",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create RICE+ Entry": {
      "main": [
        [
          {
            "node": "Parse RICE Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Jira Feature": {
      "main": [
        [
          {
            "node": "Parse Jira Feature Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse RICE Response": {
      "main": [
        [
          {
            "node": "Update Signal Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Signal Details": {
      "main": [
        [
          {
            "node": "Route Destination",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Signal Route Trigger": {
      "main": [
        [
          {
            "node": "Filter Routing Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Signal Status": {
      "main": [
        [
          {
            "node": "Build Thread Reply",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Routing Status": {
      "main": [
        [
          {
            "node": "Read Signal Details",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Sprint Response": {
      "main": [
        [
          {
            "node": "Update Signal Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Jira Bug Response": {
      "main": [
        [
          {
            "node": "Update Signal Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Sprint Backlog Item": {
      "main": [
        [
          {
            "node": "Parse Sprint Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Jira Feature Response": {
      "main": [
        [
          {
            "node": "Update Signal Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Customer Health Entry": {
      "main": [
        [
          {
            "node": "Parse Customer Health Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Customer Health Response": {
      "main": [
        [
          {
            "node": "Update Signal Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}