{
  "id": "8EEBlolGxhSkmvy9",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "feature request closer v2",
  "tags": [],
  "nodes": [
    {
      "id": "fa71b41d-03f0-4c2f-8057-48c8ef12350f",
      "name": "Tally Trigger",
      "type": "n8n-nodes-tallyforms.tallyTrigger",
      "disabled": true,
      "position": [
        -480,
        48
      ],
      "parameters": {
        "formId": "gDbZ6P"
      },
      "typeVersion": 1
    },
    {
      "id": "d6e0920b-4d08-4921-ba6f-3e1bc6f39c3e",
      "name": "Data Cleaner",
      "type": "n8n-nodes-base.set",
      "position": [
        -192,
        0
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "81db54ad-88ad-4159-896e-5d50e606a325",
              "name": "user_name",
              "type": "string",
              "value": "={{ $json.question_aaaDDD }}"
            },
            {
              "id": "4eda0e05-d795-4535-9537-f351c313be52",
              "name": "user_email",
              "type": "string",
              "value": "={{ $json.question_bbbEEE }}"
            },
            {
              "id": "20d22061-2a21-423f-b975-547c466c2883",
              "name": "original_text",
              "type": "string",
              "value": "={{ $json.question_cccFFF }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "fa1e4e46-cf5b-4a58-a593-abe62cf8c438",
      "name": "Supabase Vector Store",
      "type": "@n8n/n8n-nodes-langchain.vectorStoreSupabase",
      "position": [
        192,
        16
      ],
      "parameters": {
        "mode": "insert",
        "options": {},
        "tableName": {
          "__rl": true,
          "mode": "list",
          "value": "feature_requests",
          "cachedResultName": "feature_requests"
        }
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "a6c61c6e-7d64-481b-bdbc-26d0f61298bf",
      "name": "Embeddings OpenAI",
      "type": "@n8n/n8n-nodes-langchain.embeddingsOpenAi",
      "position": [
        160,
        256
      ],
      "parameters": {
        "model": "nomic-embed-text:latest",
        "options": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "1afaec7e-a3df-4cd9-a60f-53416f7d3901",
      "name": "Default Data Loader",
      "type": "@n8n/n8n-nodes-langchain.documentDefaultDataLoader",
      "position": [
        336,
        224
      ],
      "parameters": {
        "options": {
          "metadata": {
            "metadataValues": [
              {
                "name": "user_name",
                "value": "={{ $json.user_name }}"
              },
              {
                "name": "user_email",
                "value": "={{ $json.user_email }}"
              }
            ]
          }
        },
        "jsonData": "={{ $json.original_text }}",
        "jsonMode": "expressionData"
      },
      "typeVersion": 1.1
    },
    {
      "id": "3998b60d-7cc8-444a-85ba-c5b6c9776b5f",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -688,
        -400
      ],
      "parameters": {
        "color": 7,
        "width": 1312,
        "height": 896,
        "content": "## \ud83d\udce5 1. Capture & Memorize\nThis section cleans form data, maps fields (Name/Email), creates embeddings, and stores them in Supabase."
      },
      "typeVersion": 1
    },
    {
      "id": "84b7e470-486f-4d4d-ba79-c126efc664aa",
      "name": "When clicking \u2018Execute workflow\u2019",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        816,
        -32
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "3fc0fc03-caf4-4ceb-b928-c3eec8e2cf35",
      "name": "RSS Feed Trigger",
      "type": "n8n-nodes-base.rssFeedReadTrigger",
      "position": [
        832,
        144
      ],
      "parameters": {
        "feedUrl": "https://your_blog.url/rss/",
        "pollTimes": {
          "item": [
            {
              "mode": "everyWeek"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "aa3d1034-ffb0-43a5-9a0c-53f4ec7f776b",
      "name": "Generate Embedding (Ollama)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1584,
        64
      ],
      "parameters": {
        "url": "http://host.docker.internal:11434/api/embeddings",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "model",
              "value": "nomic-embed-text"
            },
            {
              "name": "prompt",
              "value": "={{ $json.feature_text }}"
            }
          ]
        }
      },
      "typeVersion": 4.1
    },
    {
      "id": "71da9827-231b-4e16-9ead-e3080a35885d",
      "name": "Loop Matches",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        2272,
        48
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "52c852f4-311c-4737-a995-66971c453258",
      "name": "Create Draft",
      "type": "n8n-nodes-base.gmail",
      "position": [
        2880,
        64
      ],
      "parameters": {
        "message": "={{ $json.output.body }}",
        "options": {},
        "subject": "={{ $json.output.subject }} - to:{{ $('Loop Matches').item.json.user_email }}",
        "resource": "draft"
      },
      "typeVersion": 2.1
    },
    {
      "id": "1688ef73-f02e-495d-9a0d-551cda7b27e4",
      "name": "Done!",
      "type": "n8n-nodes-base.noOp",
      "position": [
        3216,
        -352
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "9fdae0c7-328b-4b13-9d36-61715af713f5",
      "name": "Structured Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        2688,
        288
      ],
      "parameters": {
        "jsonSchemaExample": "{ \"subject\": \"...\", \"body\": \"...\" }"
      },
      "typeVersion": 1.3
    },
    {
      "id": "8dd4140a-33de-445b-9a18-c52f27d69871",
      "name": "OpenAI Chat Model3",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        2544,
        320
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "id",
          "value": "command-r7b:7b-12-2024-q8_0"
        },
        "options": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "9804ceed-770a-4522-9e39-b266051b38a9",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        656,
        -400
      ],
      "parameters": {
        "color": 7,
        "width": 1456,
        "height": 896,
        "content": "## \ud83d\udd0d 2. Trigger & Search\nTriggers on a new feature launch (RSS or Manual), embeds the description, and finds semantically similar users."
      },
      "typeVersion": 1
    },
    {
      "id": "5fe4f8d2-960a-496a-9be3-f0839538311b",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1344,
        -400
      ],
      "parameters": {
        "width": 592,
        "height": 656,
        "content": "# \ud83c\udfa3 Close the Loop: Tally to Gmail\nTurn old feature requests into new revenue by notifying users when you ship what they asked for.\n\n## How it works\n1. **Listen:** Captures Tally form submissions.\n2. **Embed:** Converts text to vectors (using Nomic/Ollama).\n3. **Store:** Saves user info & vectors to Supabase.\n4. **Match:** When a feature launches, searches Supabase for matching requests.\n5. **Act:** AI drafts a personal Gmail reply.\n\n## Setup\n* **IMPORTANT:** Copy the SQL script from the **Template Description** and run it in Supabase first.\n* Update the **HTTP Request** node with your Supabase URL."
      },
      "typeVersion": 1
    },
    {
      "id": "9abaaecb-e456-41eb-a0d7-a39f8f8a60ea",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2160,
        -400
      ],
      "parameters": {
        "color": 7,
        "width": 960,
        "height": 896,
        "content": "## \u270d\ufe0f 3. Draft Personal Emails\nIn the loops through every match and AI writes a custom email connecting their specific request to your new feature.\n* **Gmail:** Creates a DRAFT (does not send automatically)."
      },
      "typeVersion": 1
    },
    {
      "id": "d71b3d53-8556-445d-8086-56f6a08cf30d",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3152,
        -400
      ],
      "parameters": {
        "color": 4,
        "width": 496,
        "height": 384,
        "content": "\n\n\n\n\n\n\n\n\n\n\n\n# \ud83c\udf89 Done!\n\nIf the workflow reaches here, you have successfully:\n1. Found a user who felt ignored.\n2. Drafted an email showing you listened.\n3. Potentially reduced churn or gained an upsell.\n\n**Next Step:** Check your Gmail Drafts folder! \ud83d\udcee"
      },
      "typeVersion": 1
    },
    {
      "id": "14898e59-d92a-4d60-b115-218bb19032e4",
      "name": "Lunch description",
      "type": "n8n-nodes-base.set",
      "position": [
        1024,
        -32
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "5bceab7a-7012-4e90-a16b-ec6b04ca9a6b",
              "name": "description",
              "type": "string",
              "value": "we lunched dark mode"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "2808f864-51ae-4d38-bdd1-c80fca189ea8",
      "name": "Manage input - Code in JavaScript",
      "type": "n8n-nodes-base.code",
      "position": [
        1296,
        64
      ],
      "parameters": {
        "jsCode": "// 1. Get the input data (works for both Manual and RSS)\nconst input = $input.first().json;\n\n// 2. prioritize manual input, then RSS description, then RSS title\nconst text = input.contentSnippet || input.description || input.title || input.first().json.description || input.manual_text;\n\nreturn { feature_text: text };"
      },
      "typeVersion": 2
    },
    {
      "id": "bc326d78-86d3-4d96-909e-49106e027309",
      "name": "HTTP Request - Supabase Search",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1856,
        64
      ],
      "parameters": {
        "url": "https://your_link_to.supabase.co/rest/v1/rpc/match_feature_requests",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "authentication": "predefinedCredentialType",
        "bodyParameters": {
          "parameters": [
            {
              "name": "query_embedding",
              "value": "={{ $json.embedding }}"
            },
            {
              "name": "match_threshold",
              "value": "0.70"
            },
            {
              "name": "match_count",
              "value": "10"
            }
          ]
        },
        "nodeCredentialType": "supabaseApi"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "e511c78a-1219-42ae-9ac1-68f346ff5507",
      "name": "AI Agent - Draft text maker",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "onError": "continueRegularOutput",
      "position": [
        2496,
        64
      ],
      "parameters": {
        "text": "=You are a helpful product manager.\n\nContext:\n- User: {{ $json.user_name }}\n- They asked for: \"{{ $json.content }}\"\n- We launched: \"{{ $('Lunch description').item.json.description }}\"\n\nWrite a JSON object with a subject line and a short, friendly HTML email body telling them the feature is here.\n\nFormat: { \"subject\": \"...\", \"body\": \"...\" }",
        "options": {},
        "promptType": "define",
        "needsFallback": true,
        "hasOutputParser": true
      },
      "retryOnFail": true,
      "typeVersion": 2.2,
      "waitBetweenTries": 3000
    },
    {
      "id": "043f9d06-d296-40a0-af46-c3c4f6d68091",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -272,
        -208
      ],
      "parameters": {
        "color": 5,
        "width": 352,
        "height": 432,
        "content": "### \u2699\ufe0f How to Configure:\n**Data Cleaner (Important):** Open this node!\n   - Map your form's \"Name\" field to `user_name`\n   - Map your form's \"Email\" field to `user_email`\n   - Map the \"Request\" field to `original_text`\n"
      },
      "typeVersion": 1
    },
    {
      "id": "7c075856-1adf-41b1-bdd6-c6d255ffc27c",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -608,
        -208
      ],
      "parameters": {
        "color": 5,
        "width": 304,
        "height": 432,
        "content": "### \u2699\ufe0f How to Configure:\n**Tally Trigger:** Connect your form credential."
      },
      "typeVersion": 1
    },
    {
      "id": "7ea208dc-7755-4cf6-8b5c-1d8f1227bf9f",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        112,
        -208
      ],
      "parameters": {
        "color": 5,
        "width": 448,
        "height": 608,
        "content": "### \u2699\ufe0f How to Configure:\nCopy the SQL script from the **Template Description** and run it in Supabase first.\n\nThis node:\n1. **Embed:** Converts text to vectors (using Nomic/Ollama).\n2. **Store:** Saves user info & vectors to Supabase."
      },
      "typeVersion": 1
    },
    {
      "id": "517cfc80-4a11-4293-8657-3ab93f3ebd38",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        720,
        -160
      ],
      "parameters": {
        "color": 6,
        "width": 464,
        "height": 544,
        "content": "### Dual Triggers\n- **RSS Feed:** Checks your changelog weekly. If you post your product lunch it triggers.\n- **Manual:** Great for testing! Type a phrase in the 'Lunch description' node to test the search and click \"Execute\" ."
      },
      "typeVersion": 1
    },
    {
      "id": "0e8ff8fa-c79d-40ac-9b27-361e2df67b6e",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1440,
        -160
      ],
      "parameters": {
        "color": 6,
        "width": 592,
        "height": 544,
        "content": "### The Vector Search\n- **Embedding:** Converts your announcement into numbers (768 dimensions).\n- **HTTP Request:** Sends those numbers to Supabase to find the nearest semantic match.\n  - *Setup:* Update the **URL** in this node to your Supabase Project URL.\n"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "f2314c07-fb05-4418-b6f2-6030874f3a83",
  "connections": {
    "Create Draft": {
      "main": [
        [
          {
            "node": "Loop Matches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Data Cleaner": {
      "main": [
        [
          {
            "node": "Supabase Vector Store",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Matches": {
      "main": [
        [
          {
            "node": "Done!",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "AI Agent - Draft text maker",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Tally Trigger": {
      "main": [
        [
          {
            "node": "Data Cleaner",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "RSS Feed Trigger": {
      "main": [
        [
          {
            "node": "Manage input - Code in JavaScript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Embeddings OpenAI": {
      "ai_embedding": [
        [
          {
            "node": "Supabase Vector Store",
            "type": "ai_embedding",
            "index": 0
          }
        ]
      ]
    },
    "Lunch description": {
      "main": [
        [
          {
            "node": "Manage input - Code in JavaScript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model3": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent - Draft text maker",
            "type": "ai_languageModel",
            "index": 0
          },
          {
            "node": "AI Agent - Draft text maker",
            "type": "ai_languageModel",
            "index": 1
          }
        ]
      ]
    },
    "Default Data Loader": {
      "ai_document": [
        [
          {
            "node": "Supabase Vector Store",
            "type": "ai_document",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "AI Agent - Draft text maker",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent - Draft text maker": {
      "main": [
        [
          {
            "node": "Create Draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Embedding (Ollama)": {
      "main": [
        [
          {
            "node": "HTTP Request - Supabase Search",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request - Supabase Search": {
      "main": [
        [
          {
            "node": "Loop Matches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Manage input - Code in JavaScript": {
      "main": [
        [
          {
            "node": "Generate Embedding (Ollama)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking \u2018Execute workflow\u2019": {
      "main": [
        [
          {
            "node": "Lunch description",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}