{
  "name": "Signal-to-Sequence Translator",
  "nodes": [
    {
      "parameters": {
        "trigger": [
          "app_mention",
          "reaction_added"
        ],
        "watchWorkspace": true,
        "options": {
          "resolveIds": true,
          "reactionEmojis": "white_check_mark"
        }
      },
      "id": "9273ccbe-1f81-4f75-b272-52f1ead6539a",
      "name": "Signal Received",
      "type": "n8n-nodes-base.slackTrigger",
      "typeVersion": 1,
      "position": [
        0,
        464
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "signal-text",
              "name": "signalText",
              "value": "={{ $json.text.replace(/<@[^>]+>/g, \"\").trim() }}",
              "type": "string"
            },
            {
              "id": "signal-channel",
              "name": "channel",
              "value": "={{ $json.channel }}",
              "type": "string"
            },
            {
              "id": "signal-thread",
              "name": "threadTs",
              "value": "={{ $json.thread_ts || $json.ts }}",
              "type": "string"
            },
            {
              "id": "signal-user",
              "name": "userId",
              "value": "={{ $json.user }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "47ae7029-7431-442e-8fa9-00a5fcbfd48b",
      "name": "Extract Signal",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        448,
        256
      ]
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=A rep just posted this real-time buying signal about a target account:\n\n\"{{ $json.signalText }}\"\n\nClassify the signal type, then draft TWO distinctly different 3-touch outbound email sequences (Angle A and Angle B) that each reference this specific signal but take a genuinely different strategic approach - not reworded versions of each other. Each email needs a subject line and a short body (under 120 words). Keep tone consultative, not salesy.",
        "hasOutputParser": true,
        "options": {
          "systemMessage": "You are a GTM signal-to-sequence assistant for Fortavault, Inc., a fictional data protection and security platform (cloud backup, ransomware recovery, zero-trust access, and compliance reporting). You take a plain-language, real-time buying signal and translate it into two distinctly different ready-to-send 3-touch outbound email sequences. You have no tools - work only from the signal text you are given and the conversation history.\n\nCompany context (reference specific products and competitive positioning where it strengthens an angle, but don't force it if irrelevant):\n\nProducts: Fortavault Backup Cloud (cloud backup & disaster recovery), Fortavault Shield (ransomware detection, isolation, one-click rollback), Fortavault Access (zero-trust endpoint access control), Fortavault Insights (compliance & audit reporting - HIPAA, SOC 2, GLBA).\n\nIndustries served: Financial Services, Healthcare, Legal, Manufacturing, Higher Education, Retail/E-commerce, Insurance.\n\nKey competitors and how Fortavault wins: Veyron Data (backup/DR - Fortavault is faster to recover, simpler SaaS-data setup), Ashcombe Systems (ransomware detection point solution - Fortavault pairs detection with backup for one-click recovery), Cyphertide (zero-trust access - Fortavault is better suited for regulated industries needing audit-ready logs), Northgate Cloud Security (broad platform incumbent - Fortavault has a more focused roadmap and faster implementation), Ridgeline Compliance (compliance reporting point tool - Fortavault Insights is natively integrated with backup/security data).\n\nTWO INPUT TYPES - handle each differently:\n\n1) NEW SIGNAL: The input describes a real-world buying signal about a target account (funding, layoffs, leadership change, security incident, acquisition, product launch, expansion, job posting, etc). Classify the signal type, then draft two full 3-touch sequences from scratch (Angle A: risk/urgency framing, Angle B: consultative/relationship framing). Both angles are new, so changedAngles is \"A,B\".\n\n2) REFINEMENT INSTRUCTION: The input is an editing/formatting instruction about the sequences you already drafted in this conversation (e.g. \"make angle b shorter\", \"make touch 2 punchier\", \"remove the stat in angle a\"). This is NOT a new signal - do not ask for clarification. Look at your most recent prior JSON response in the conversation history, apply the requested edit ONLY to the angle(s) named or clearly implied, and copy the untouched angle's text through unchanged. Set changedAngles to just the angle(s) you actually edited - e.g. \"B\" if only angle B changed, \"A\" if only angle A changed, \"A,B\" if the instruction applies to both (e.g. \"make both shorter\"). If ambiguous which prior draft it refers to, apply it to the most recent angles you drafted.\n\nRespond with a JSON object containing exactly four fields: signalType (one short label such as funding, layoffs, leadership_change, security_incident, acquisition, product_launch, expansion, or other - keep this unchanged on refinements), angleA (full 3-touch sequence, Slack markdown bold via asterisks, no headers, ending with the exact line 'React with a \u2705 on this message to approve and log this angle.'), angleB (same formatting and ending line), and changedAngles (a string: \"A\", \"B\", or \"A,B\" - exactly which angle(s) are new or were just edited this turn). Never respond with plain conversational text, questions, or anything outside this JSON schema."
        }
      },
      "id": "964c5f8a-8007-40b4-b2cc-589775e1346a",
      "name": "Draft Sequence Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 3.1,
      "position": [
        672,
        48
      ]
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "claude-sonnet-4-6",
          "cachedResultName": "Claude Sonnet 4.6"
        },
        "options": {
          "temperature": 0.6
        }
      },
      "id": "608dec29-958b-44eb-92c1-c952fc77734a",
      "name": "Anthropic Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "typeVersion": 1.5,
      "position": [
        808,
        488
      ]
    },
    {
      "parameters": {
        "sessionIdType": "customKey",
        "sessionKey": "={{ $('Extract Signal').item.json.threadTs }}"
      },
      "id": "b464a8f1-164f-43fc-a6cc-a41c750cdc85",
      "name": "Simple Memory",
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "typeVersion": 1.4,
      "position": [
        744,
        280
      ]
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Extract Signal').item.json.channel }}"
        },
        "text": "=\ud83d\udccb *Angle A*\n\n{{ $json.output.angleA }}",
        "otherOptions": {
          "thread_ts": {
            "replyValues": {
              "thread_ts": "={{ $('Extract Signal').item.json.threadTs }}"
            }
          }
        }
      },
      "id": "352a4472-1d7f-4879-8bfc-d8b20e062e67",
      "name": "Post Angle A to Slack",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.7,
      "position": [
        1472,
        160
      ]
    },
    {
      "parameters": {
        "jsonSchemaExample": "{ \"signalType\": \"funding\", \"angleA\": \"full angle A 3-touch sequence text\", \"angleB\": \"full angle B 3-touch sequence text\", \"changedAngles\": \"A,B\" }",
        "autoFix": true
      },
      "id": "a1e717ca-a827-4e85-88c9-68ac0044b69d",
      "name": "Structured Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "typeVersion": 1.3,
      "position": [
        872,
        280
      ]
    },
    {
      "parameters": {
        "dataTableId": {
          "__rl": true,
          "mode": "id",
          "value": "2OTlcSU4FXFOxqAt",
          "cachedResultName": "GTM Signal Log"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "signalText": "={{ $('Extract Signal').item.json.signalText }}",
            "signalType": "={{ $('Draft Sequence Agent').item.json.output.signalType }}",
            "angleLabel": "A",
            "draftText": "={{ $('Draft Sequence Agent').item.json.output.angleA }}",
            "channel": "={{ $('Extract Signal').item.json.channel }}",
            "threadTs": "={{ $('Extract Signal').item.json.threadTs }}",
            "messageTs": "={{ $json.message_timestamp }}",
            "status": "drafted",
            "loggedAt": "={{ $now.toISO() }}"
          }
        },
        "options": {}
      },
      "id": "f5c1ab2d-0ba3-4f37-aa0e-dc8a87077fa0",
      "name": "Log Angle A Row",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [
        1696,
        160
      ]
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Extract Signal').item.json.channel }}"
        },
        "text": "=\ud83d\udccb *Angle B*\n\n{{ $('Draft Sequence Agent').item.json.output.angleB }}",
        "otherOptions": {
          "thread_ts": {
            "replyValues": {
              "thread_ts": "={{ $('Extract Signal').item.json.threadTs }}"
            }
          }
        }
      },
      "id": "0f82f0a9-5383-4672-9f53-beb2c0cc0455",
      "name": "Post Angle B to Slack",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.7,
      "position": [
        1472,
        368
      ]
    },
    {
      "parameters": {
        "dataTableId": {
          "__rl": true,
          "mode": "id",
          "value": "2OTlcSU4FXFOxqAt",
          "cachedResultName": "GTM Signal Log"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "signalText": "={{ $('Extract Signal').item.json.signalText }}",
            "signalType": "={{ $('Draft Sequence Agent').item.json.output.signalType }}",
            "angleLabel": "B",
            "draftText": "={{ $('Draft Sequence Agent').item.json.output.angleB }}",
            "channel": "={{ $('Extract Signal').item.json.channel }}",
            "threadTs": "={{ $('Extract Signal').item.json.threadTs }}",
            "messageTs": "={{ $json.message_timestamp }}",
            "status": "drafted",
            "loggedAt": "={{ $now.toISO() }}"
          }
        },
        "options": {}
      },
      "id": "8c8fd408-5d5a-46a3-b264-452cd68df539",
      "name": "Log Angle B Row",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [
        1696,
        368
      ]
    },
    {
      "parameters": {
        "operation": "get",
        "dataTableId": {
          "__rl": true,
          "mode": "id",
          "value": "2OTlcSU4FXFOxqAt",
          "cachedResultName": "GTM Signal Log"
        },
        "filters": {
          "conditions": [
            {
              "keyName": "messageTs",
              "keyValue": "={{ $json.item.ts }}"
            }
          ]
        },
        "limit": 1
      },
      "id": "a4b2e272-2722-4d23-8653-987ab33b3d6e",
      "name": "Find Log Row",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [
        448,
        656
      ]
    },
    {
      "parameters": {
        "operation": "update",
        "dataTableId": {
          "__rl": true,
          "mode": "id",
          "value": "2OTlcSU4FXFOxqAt",
          "cachedResultName": "GTM Signal Log"
        },
        "filters": {
          "conditions": [
            {
              "keyName": "messageTs",
              "keyValue": "={{ $('Signal Received').item.json.item.ts }}"
            }
          ]
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "status": "approved",
            "approvedAt": "={{ $now.toISO() }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "signalText",
              "displayName": "signalText",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "signalType",
              "displayName": "signalType",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "angleLabel",
              "displayName": "angleLabel",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "draftText",
              "displayName": "draftText",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "channel",
              "displayName": "channel",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "threadTs",
              "displayName": "threadTs",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "messageTs",
              "displayName": "messageTs",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "status",
              "displayName": "status",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "loggedAt",
              "displayName": "loggedAt",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "dateTime",
              "readOnly": false,
              "removed": false
            },
            {
              "id": "approvedAt",
              "displayName": "approvedAt",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "dateTime",
              "readOnly": false,
              "removed": false
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "id": "442f79b7-9cbf-48e4-bdde-64cd55a11453",
      "name": "Update Log Row",
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1.1,
      "position": [
        848,
        656
      ]
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Signal Received').item.json.item.channel }}"
        },
        "text": "=\u2705 Angle {{ $('Find Log Row').item.json.angleLabel }} approved and logged \u2014 ready to send.",
        "otherOptions": {
          "thread_ts": {
            "replyValues": {
              "thread_ts": "={{ $('Find Log Row').item.json.threadTs }}"
            }
          }
        }
      },
      "id": "30f9947c-5bee-470a-aaf6-1fbb20db61fd",
      "name": "Post Approval Confirmation",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.7,
      "position": [
        1248,
        560
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 1
          },
          "conditions": [
            {
              "leftValue": "={{ $json.type }}",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "rightValue": "app_mention"
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "8ae7fe2e-bb57-48fa-b3e2-7db55b5756e9",
      "name": "Route by Event Type",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        224,
        464
      ]
    },
    {
      "parameters": {
        "resource": "draft",
        "subject": "={{ $json.subject }}",
        "message": "={{ $json.body }}",
        "options": {
          "sendTo": "adamgiuriceo@gmail.com"
        }
      },
      "id": "2a5dd092-b0cb-4842-9b6c-50f3f7ffb64b",
      "name": "Create Gmail Draft",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.2,
      "position": [
        1472,
        752
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 1
          },
          "conditions": [
            {
              "leftValue": "={{ $json.output.changedAngles }}",
              "operator": {
                "type": "string",
                "operation": "contains"
              },
              "rightValue": "A"
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "8087d194-ea76-4fe4-b2ca-ae108b208f3e",
      "name": "Angle A Changed?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1248,
        160
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 1
          },
          "conditions": [
            {
              "leftValue": "={{ $json.output.changedAngles }}",
              "operator": {
                "type": "string",
                "operation": "contains"
              },
              "rightValue": "B"
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "78da3fbc-ad9b-45c5-b3c9-c12cf2f048ae",
      "name": "Angle B Changed?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1248,
        368
      ]
    },
    {
      "parameters": {
        "jsCode": "const src = $('Find Log Row').first().json;\nlet text = src.draftText || '';\n\n// Strip the trailing approval-reaction line - it's a Slack instruction, not part of the email\ntext = text.replace(/\\n*React with a[\\s\\S]*?approve and log this angle\\.\\s*$/i, '').trim();\n\n// Find each touch block directly by its markers, whether or not '---' separators are present\n// (the model doesn't always include them) and whether or not there's a blank line after Subject\nconst touchRegex = /\\*Touch\\s*(\\d+)\\*\\s*\\n\\*Subject:\\s*(.+?)\\*\\s*\\n+([\\s\\S]*?)(?=(?:\\n+-{2,}\\n+)?\\*Touch\\s*\\d+\\*|$)/gi;\nconst out = [];\nlet m;\nwhile ((m = touchRegex.exec(text)) !== null) {\n  const [, touchNumber, subject, rawBody] = m;\n  const body = rawBody.trim().replace(/-{3,}\\s*$/, '').trim().replace(/\\*(.+?)\\*/g, '$1');\n  out.push({\n    json: {\n      touchNumber: Number(touchNumber),\n      subject: subject.trim(),\n      body,\n      angleLabel: src.angleLabel,\n      signalType: src.signalType,\n      threadTs: src.threadTs,\n      channel: src.channel\n    }\n  });\n}\n\nreturn out;\n"
      },
      "id": "5a10ea12-b1b1-4668-a229-dea71cfc817f",
      "name": "Split Touches",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1248,
        752
      ]
    }
  ],
  "connections": {
    "Extract Signal": {
      "main": [
        [
          {
            "node": "Draft Sequence Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Anthropic Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Draft Sequence Agent",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "Structured Output Parser",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Simple Memory": {
      "ai_memory": [
        [
          {
            "node": "Draft Sequence Agent",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Draft Sequence Agent",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Post Angle A to Slack": {
      "main": [
        [
          {
            "node": "Log Angle A Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Post Angle B to Slack": {
      "main": [
        [
          {
            "node": "Log Angle B Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find Log Row": {
      "main": [
        [
          {
            "node": "Update Log Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Log Row": {
      "main": [
        [
          {
            "node": "Post Approval Confirmation",
            "type": "main",
            "index": 0
          },
          {
            "node": "Split Touches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Signal Received": {
      "main": [
        [
          {
            "node": "Route by Event Type",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Event Type": {
      "main": [
        [
          {
            "node": "Extract Signal",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Find Log Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Draft Sequence Agent": {
      "main": [
        [
          {
            "node": "Angle A Changed?",
            "type": "main",
            "index": 0
          },
          {
            "node": "Angle B Changed?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Angle A Changed?": {
      "main": [
        [
          {
            "node": "Post Angle A to Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Angle B Changed?": {
      "main": [
        [
          {
            "node": "Post Angle B to Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Touches": {
      "main": [
        [
          {
            "node": "Create Gmail Draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "availableInMCP": true,
    "binaryMode": "separate"
  }
}