{
  "name": "Support Inbox Router",
  "tags": [],
  "nodes": [
    {
      "id": "fc76d961-5ceb-4f54-9cf3-27a709b93cc1",
      "name": "New Support Email",
      "type": "n8n-nodes-base.gmailTrigger",
      "position": [
        -112,
        208
      ],
      "parameters": {
        "simple": false,
        "filters": {},
        "options": {
          "downloadAttachments": false
        },
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "aa4e48cd-f983-4608-832e-a54838190a2e",
      "name": "Route by Category",
      "type": "n8n-nodes-base.switch",
      "position": [
        1248,
        176
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "ccc6cbfd-6759-4227-8bfa-6943faf39289",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.data.category }}",
                    "rightValue": "billing"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "d80d3596-0ffb-4100-9625-1945a4064a7b",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.data.category }}",
                    "rightValue": "technical"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "version": 1,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "88120474-c4f5-4850-bbef-2a6412ecc8ab",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.data.category }}",
                    "rightValue": "sales"
                  }
                ]
              }
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra",
          "renameFallbackOutput": "other"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "16b11847-6651-4d2e-829b-6b6283dac334",
      "name": "Slack: Billing",
      "type": "n8n-nodes-base.slack",
      "position": [
        1600,
        0
      ],
      "parameters": {
        "text": "==:credit_card: {{ {\"urgent\": \"\ud83d\udea8\", \"normal\": \"\ud83d\udfe2\", \"low\": \"\u26aa\"}[$('easybits: Classify & Score Email').item.json.data.priority] }} *{{ $('easybits: Classify & Score Email').item.json.data.priority.toUpperCase() }} priority \u2014 billing*\n*From:* {{ $('New Support Email').item.json.from.value[0].address }}\n*Subject:* {{ $('New Support Email').item.json.subject }}\n*Summary:* {{ $('easybits: Classify & Score Email').item.json.data.summary }}\n*Confidence:* {{ $('easybits: Classify & Score Email').item.json.data.confidence }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": ""
        },
        "otherOptions": {}
      },
      "typeVersion": 2.2
    },
    {
      "id": "d6bf4180-b46c-4663-8785-e9aab75cd148",
      "name": "Slack: Technical",
      "type": "n8n-nodes-base.slack",
      "position": [
        1600,
        160
      ],
      "parameters": {
        "text": "==:wrench: {{ {\"urgent\": \"\ud83d\udea8\", \"normal\": \"\ud83d\udfe2\", \"low\": \"\u26aa\"}[$('easybits: Classify & Score Email').item.json.data.priority] }} *{{ $('easybits: Classify & Score Email').item.json.data.priority.toUpperCase() }} priority \u2014 technical*\n*From:* {{ $('New Support Email').item.json.from.value[0].address }}\n*Subject:* {{ $('New Support Email').item.json.subject }}\n*Summary:* {{ $('easybits: Classify & Score Email').item.json.data.summary }}\n*Confidence:* {{ $('easybits: Classify & Score Email').item.json.data.confidence }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": ""
        },
        "otherOptions": {}
      },
      "typeVersion": 2.2
    },
    {
      "id": "5dfd9c45-b7ae-4cb0-8599-f50e10e887f1",
      "name": "Slack: Sales",
      "type": "n8n-nodes-base.slack",
      "position": [
        1600,
        320
      ],
      "parameters": {
        "text": "==:briefcase: {{ {\"urgent\": \"\ud83d\udea8\", \"normal\": \"\ud83d\udfe2\", \"low\": \"\u26aa\"}[$('easybits: Classify & Score Email').item.json.data.priority] }} *{{ $('easybits: Classify & Score Email').item.json.data.priority.toUpperCase() }} priority \u2014 sales*\n*From:* {{ $('New Support Email').item.json.from.value[0].address }}\n*Subject:* {{ $('New Support Email').item.json.subject }}\n*Summary:* {{ $('easybits: Classify & Score Email').item.json.data.summary }}\n*Confidence:* {{ $('easybits: Classify & Score Email').item.json.data.confidence }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": ""
        },
        "otherOptions": {}
      },
      "typeVersion": 2.2
    },
    {
      "id": "d83b2eab-b2f0-461b-87e7-d4f60fd1a3d0",
      "name": "Slack: Other",
      "type": "n8n-nodes-base.slack",
      "position": [
        1600,
        480
      ],
      "parameters": {
        "text": "==:inbox_tray: {{ {\"urgent\": \"\ud83d\udea8\", \"normal\": \"\ud83d\udfe2\", \"low\": \"\u26aa\"}[$('easybits: Classify & Score Email').item.json.data.priority] }} *{{ $('easybits: Classify & Score Email').item.json.data.priority.toUpperCase() }} priority \u2014 uncategorized*\n*From:* {{ $('New Support Email').item.json.from.value[0].address }}\n*Subject:* {{ $('New Support Email').item.json.subject }}\n*Summary:* {{ $('easybits: Classify & Score Email').item.json.data.summary }}\n*Confidence:* {{ $('easybits: Classify & Score Email').item.json.data.confidence }}{{ $('easybits: Classify & Score Email').item.json.data.confidence === 'low' ? ' \u26a0\ufe0f _Routed here due to low confidence \u2014 model\\'s best guess was: ' + $('easybits: Classify & Score Email').item.json.data.category + '_' : '' }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": ""
        },
        "otherOptions": {}
      },
      "typeVersion": 2.2
    },
    {
      "id": "03fbec4e-5b27-4455-bbd2-38b80838ac19",
      "name": "easybits: Classify & Score Email",
      "type": "@easybits/n8n-nodes-extractor.easybitsExtractor",
      "position": [
        560,
        208
      ],
      "parameters": {},
      "typeVersion": 2
    },
    {
      "id": "824c968c-a5f5-4820-8856-9b7a01c4a4d1",
      "name": "Reroute Low Confidence to Other",
      "type": "n8n-nodes-base.set",
      "position": [
        944,
        208
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "1af5fc0c-e4b6-46e6-a434-4e8fd16b4bf1",
              "name": "data.category",
              "type": "string",
              "value": "={{ $json.data.confidence === 'low' ? 'other' : $json.data.category }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "26675fb4-1990-44ca-80b0-8bf50fdb104a",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -208,
        -48
      ],
      "parameters": {
        "color": 7,
        "width": 288,
        "height": 448,
        "content": "## \ud83d\udce5 New Support Email\nPolls your support inbox for **new emails**. Set to **Every Minute** for testing \u2013 bump to Every 5 Minutes in production. Add a label filter (e.g. `support`) to keep personal mail out of the classifier."
      },
      "typeVersion": 1
    },
    {
      "id": "3c8eb649-8f6f-4cc5-824d-a84f86ed21d0",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        96,
        -48
      ],
      "parameters": {
        "color": 7,
        "width": 288,
        "height": 448,
        "content": "## \ud83d\udcc4 Prepare Email for Extraction\nPackages the **email content** into a format that's ready to be processed by the Extractor pipeline. **Don't modify the code** \u2013 just paste it as-is. Handles long emails, weird characters, and missing fields safely."
      },
      "typeVersion": 1
    },
    {
      "id": "53b109d9-f664-459b-b118-f80817e2ce16",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        400,
        -48
      ],
      "parameters": {
        "color": 7,
        "width": 432,
        "height": 448,
        "content": "## \ud83e\udd16 easybits: Classify & Score Email\nOne Extractor call returns four fields in a single pass:\n- **category** \u2192 billing / technical / sales / other\n- **summary** \u2192 1\u20132 sentence factual summary (in the email's language)\n- **confidence** \u2192 high / mid / low\n- **priority** \u2192 urgent / normal / low (sentiment + urgency)\n\nBilingual prompts handle **English and German** emails with the same rigor."
      },
      "typeVersion": 1
    },
    {
      "id": "92142a6f-172d-430e-8c9f-bcbb77c28590",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        848,
        -48
      ],
      "parameters": {
        "color": 7,
        "width": 288,
        "height": 448,
        "content": "## \ud83d\udedf Reroute Low Confidence to Other\nSafety net step. If the Extractor returns **low confidence**, the category is rewritten to `other` so the email lands in the catch-all channel. The model's original best guess is preserved in the data and surfaced in the Slack message \u2013 useful for spotting prompts that need tuning."
      },
      "typeVersion": 1
    },
    {
      "id": "d2a74013-e6b5-493a-bb7a-be516ab9a964",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1152,
        -48
      ],
      "parameters": {
        "color": 7,
        "width": 288,
        "height": 448,
        "content": "## \ud83d\udd00 Route by Category\nReads the (possibly rewritten) `category` field and sends the email down one of four paths. The fallback output catches **any unmatched value** \u2013 including everything routed here by the low-confidence override."
      },
      "typeVersion": 1
    },
    {
      "id": "6f0c7276-2413-485e-bdd2-895464a16dd6",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1456,
        -288
      ],
      "parameters": {
        "color": 7,
        "width": 400,
        "height": 944,
        "content": "## \ud83d\udcac Slack: Per-Category Channels\nFour channels, one Slack node each. Every message includes:\n- Priority badge (\ud83d\udea8 / \ud83d\udfe2 / \u26aa) and label\n- Sender, subject, and 1\u20132 sentence summary\n- Confidence level\n- (For Other only) a breadcrumb showing the model's best guess if rerouted by low confidence\n\nReplace the channel field in each node with your real channel name or ID."
      },
      "typeVersion": 1
    },
    {
      "id": "3e135580-905b-4bc9-b0d3-7da730ec6890",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1056,
        -624
      ],
      "parameters": {
        "width": 832,
        "height": 1648,
        "content": "# \ud83d\udcec Support Inbox Router\n(powered by easybits)\n\n## What This Workflow Does\nWatches your support inbox, classifies every incoming email by category, scores it for **priority** based on sentiment and urgency signals, and posts a clean summary to the matching Slack channel. Works equally well for **English and German** emails. Low-confidence classifications automatically fall through to the catch-all channel so nothing slips through, with the model's best guess preserved as a breadcrumb.\n\n## How It Works\n1. **Watch** \u2013 Gmail Trigger polls your support inbox for new mail\n2. **Prepare** \u2013 The email content is packaged into a format ready for extraction\n3. **Classify** \u2013 easybits returns category, summary, confidence, and priority in one call\n4. **Route** \u2013 Low-confidence emails are folded into \"Other\"; the rest go by category\n5. **Notify** \u2013 A formatted message lands in the matching Slack channel with priority emoji\n\n## Categories\n- \ud83d\udcb3 **Billing** \u2013 invoices, payments, refunds, subscription changes\n- \ud83d\udd27 **Technical** \u2013 bug reports, errors, login issues\n- \ud83d\udcbc **Sales** \u2013 pricing questions, demo requests, new prospects\n- \ud83d\udce5 **Other** \u2013 everything else, plus low-confidence fallbacks\n\n## Priority Levels\n- \ud83d\udea8 **Urgent** \u2013 angry tone, things broken, hot leads, time-sensitive\n- \ud83d\udfe2 **Normal** \u2013 standard requests, no time pressure\n- \u26aa **Low** \u2013 casual inquiries, FYIs, vendor pitches\n\n## Setup Guide\n### 1. Install the easybits Extractor Node\nAlready available out of the box on **n8n Cloud**. On self-hosted instances: Settings \u2192 Community Nodes \u2192 Install `@easybits/n8n-nodes-extractor`.\n\n### 2. Connect Your Credentials\n- **Gmail** on the trigger node (OAuth)\n- **easybits** on the classify node (API key from easybits.tech)\n- **Slack** on all four channel nodes (one credential, reused four times)\n\n### 3. Set Up the Extractor Pipeline\nCreate an easybits pipeline with four fields. Copy each prompt below into the matching field's description:\n\n- \ud83d\udcc2 [Category Classification Prompt](https://github.com/felix-sattler-easybits/n8n-workflows/blob/a62d2189ed8ee72424335454fdf82fc735c2b14a/easybits-support-inbox-router/easybits_category_classification_prompt.md)\n- \ud83d\udcdd [Summary Prompt](https://github.com/felix-sattler-easybits/n8n-workflows/blob/a62d2189ed8ee72424335454fdf82fc735c2b14a/easybits-support-inbox-router/easybits_summary_prompt.md)\n- \ud83c\udfaf [Confidence Prompt](https://github.com/felix-sattler-easybits/n8n-workflows/blob/a62d2189ed8ee72424335454fdf82fc735c2b14a/easybits-support-inbox-router/easybits_confidence_prompt.md)\n- \ud83d\udea6 [Priority Prompt](https://github.com/felix-sattler-easybits/n8n-workflows/blob/a62d2189ed8ee72424335454fdf82fc735c2b14a/easybits-support-inbox-router/easybits_priority_prompt.md)\n\n### 4. Set Up Slack Channels\nCreate four channels in your workspace and update each Slack node to point at the right one:\n- `#support-billing`\n- `#support-technical`\n- `#support-sales`\n- `#support-other`\n\n### 5. (Optional) Filter the Inbox\nOn the Gmail Trigger, add a label filter (e.g. `support`) so personal mail doesn't get classified.\n\n### 6. Activate & Test\nSend a few test emails covering different categories and tones. Verify they land in the right channels with sensible priority emoji before going live.\n\n## Notes\n- **Bilingual prompt** \u2013 works on English and German emails out of the box, with German summaries for German emails\n- **No confidence gate** \u2013 low-confidence emails route to `#support-other` with a flag noting the model's best guess, so reviewers can spot patterns without a separate channel\n- **One Extractor call** \u2013 classification, summary, confidence, and priority all come from a single pipeline call, keeping latency and cost low"
      },
      "typeVersion": 1
    },
    {
      "id": "752bc6ba-a2f5-4313-ae1c-9e1678f9e4f3",
      "name": "Prepare Email",
      "type": "n8n-nodes-base.code",
      "position": [
        176,
        208
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Packages the incoming email into a format that the easybits Extractor can read.\n// You don't need to understand or modify this \u2014 just paste it as-is.\n\nconst email = $input.item.json;\n\n// Pull the fields out of the Gmail node, with safe fallbacks\nconst subject = email.subject || '(no subject)';\nconst from = email.from?.value?.[0]?.address || email.from?.text || 'unknown sender';\nconst date = email.date || '';\nconst body = email.text || email.snippet || '(empty body)';\n\n// Build the document content\nconst content = `Subject: ${subject}\nFrom: ${from}\nDate: ${date}\n\n${body}`;\n\n// Escape characters that have special meaning in PDFs\nconst escape = (s) => s.replace(/\\\\/g, '\\\\\\\\').replace(/\\(/g, '\\\\(').replace(/\\)/g, '\\\\)');\n\n// Wrap long lines so they fit on the page\nconst lines = content.split('\\n').flatMap((line) => {\n  if (line.length === 0) return [''];\n  const wrapped = [];\n  for (let i = 0; i < line.length; i += 90) {\n    wrapped.push(line.slice(i, i + 90));\n  }\n  return wrapped;\n});\n\n// Build the text stream that the PDF will render\nlet stream = 'BT\\n/F1 10 Tf\\n50 780 Td\\n12 TL\\n';\nfor (const line of lines) {\n  stream += `(${escape(line)}) Tj\\nT*\\n`;\n}\nstream += 'ET';\n\nconst streamLength = Buffer.byteLength(stream, 'utf-8');\n\n// Assemble the PDF structure\nconst objects = [\n  '1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj',\n  '2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj',\n  '3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >> endobj',\n  `4 0 obj << /Length ${streamLength} >> stream\\n${stream}\\nendstream endobj`,\n  '5 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> endobj',\n];\n\n// Calculate byte offsets for the cross-reference table\nconst header = '%PDF-1.4\\n';\nconst offsets = [0];\nlet cursor = Buffer.byteLength(header, 'utf-8');\nfor (const obj of objects) {\n  offsets.push(cursor);\n  cursor += Buffer.byteLength(obj + '\\n', 'utf-8');\n}\n\n// Build the xref table\nlet xref = `xref\\n0 ${objects.length + 1}\\n+1234567890 f \\n`;\nfor (let i = 1; i <= objects.length; i++) {\n  xref += `${offsets[i].toString().padStart(10, '0')} 00000 n \\n`;\n}\n\nconst trailer = `trailer << /Size ${objects.length + 1} /Root 1 0 R >>\\nstartxref\\n${cursor}\\n%%EOF`;\n\nconst pdf = header + objects.join('\\n') + '\\n' + xref + trailer;\n\n// Hand the document off to the next node as a binary file\nreturn {\n  json: { subject, from, date },\n  binary: {\n    data: {\n      data: Buffer.from(pdf, 'binary').toString('base64'),\n      mimeType: 'application/pdf',\n      fileName: 'email.pdf',\n    },\n  },\n};"
      },
      "typeVersion": 2
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "Prepare Email": {
      "main": [
        [
          {
            "node": "easybits: Classify & Score Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "New Support Email": {
      "main": [
        [
          {
            "node": "Prepare Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Category": {
      "main": [
        [
          {
            "node": "Slack: Billing",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack: Technical",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack: Sales",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack: Other",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Reroute Low Confidence to Other": {
      "main": [
        [
          {
            "node": "Route by Category",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "easybits: Classify & Score Email": {
      "main": [
        [
          {
            "node": "Reroute Low Confidence to Other",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}