Demo build by Kedgework. Not client work. Synthetic data.

hello@kedgework.com

All demo builds

AI support inbox triage

Common support questions get a draft reply written only from your approved answers, urgent and risky messages go straight to a person, and in the first weeks nothing goes out until a person approves it.

ExamplePay is a fictional payments company. Every message shown here was invented for the demo.

Offline test
79 passed, 0 failed, run on 2026-10-05. No network and no API key used.
Inside a real n8n 2.41.7
18 passed, 0 failed, run on 2026-10-05. No internet and no real key used.
Live test with Claude
Not run yet. Claude's accuracy on real messages needs real messages and an API key.
Workflow
n8n, 42 nodes. 17 credential slots, all placeholders until setup. Download

What it solves

A payments company gets the same questions all day: fees, limits, "where is my transfer". Mixed in are a few that cannot wait, like an unrecognised payment or a legal threat, and they wait behind the easy ones.

What the flow does

  1. A message arrives by email, web chat or WhatsApp. The flow confirms receipt to your inbox tool at once. A copy with the same message id is dropped until the next 08:00 check clears the list, so a quick retry from your tool gets no second answer; a resend after 08:00 counts as new.
  2. Claude reads it: topic, language (Spanish, Portuguese, English), mood, and a short draft reply, only when your approved answers cover the question.
  3. Fixed rules decide, not the AI. A person always gets the message when:
    • it mentions fraud, a payment the customer did not make, the police or a lawyer (marked urgent, even when the AI call fails)
    • it is about identity checks, refunds or disputes
    • a payment question involves 1,000 USD or more
    • the customer is angry, or your inbox tool reports a third message on the same issue
    • the AI is unsure, or its draft contains a number that is not in your approved answers
  4. Payment questions with a reference are checked in your payments system (read only) and answered from a fixed template, never from the AI.
  5. A person approves, then the reply goes out. For the first weeks every draft is posted in Slack with two buttons, Approve and send or Decline, answer by hand. Declined drafts, and drafts with no click within 4 hours, go to your team to answer by hand.
Mock-up of the approval message in the Slack channel #support-drafts: a reply waiting for approval for the invented message fx01, a question about the fee for an international transfer of 200 USD, the draft reply, and two buttons, Decline, answer by hand, and Approve and send. With no click within 4 hours it goes to #support-team.
Mock-up of the approval message, built with n8n's own Slack message code from the workflow's template and the invented message fx01. Not a screenshot of Slack.

In this demo, replies go out by email. For web chat and WhatsApp the approved reply is posted in Slack for a person to send, until we connect your chat provider. The payment check matches the sender's email with the payment owner, so there a payment question goes to a person unless your chat tool passes a verified email.

If Slack is down, alerts for urgent messages and for the team are tried 3 times, then sent by email to a team address you choose.

Checked every morning at 08:00, in your time zone

You get one line in Slack, problems also by email, and a ping to an outside monitor in case the check itself does not run.

Diagram

Diagram of the flow. 01: a message arrives by email, web chat or WhatsApp, through a webhook. 02: Claude reads it. 03: fixed rules decide between five outcomes: answer sent, payment checked, to your team, urgent to your team, to sales. 04: every decision is logged, then checked every morning at 08:00, your time: the log, a self-test with two test messages, failed runs, and one line in Slack.
How a message moves through the flow

What you import into n8n

The workflow as the n8n editor draws it. Top row, triage: inbound message, settings, normalize message, respond to webhook, skip repeated messages, Claude classify and draft, parse and apply rules, then a route to the payment lookup, the Slack approval, escalations, the sales lead and the email steps. Bottom row, the morning check at 08:00: forget the message ids from yesterday, send self-test messages, wait for the test runs, read the last 24 hours of the log, failed runs from the n8n API, morning summary in Slack, email to the owner when problems are found, heartbeat ping. Red marks show credentials that are not connected yet.
workflow.json opened in a local n8n 2.41.7. The red marks mean "credential not connected yet": every credential is a placeholder until setup.

How it was tested

From the test files

Copied as they are, line by line. The full files are below.

test/RESULT.txt, lines 2 to 4

Offline test run: 2026-10-05T19:31:58.427Z, node v26.3.0, command: node test/run-tests.mjs
No network and no API key used. The AI answers below are mock answers from fixtures/messages.json.
This test does NOT measure how accurately the real model classifies messages.

test/RESULT.txt, line 120

TOTAL: 79 passed, 0 failed

test/N8N-RUN.txt, lines 2 to 3

Run in a real n8n: 2026-10-05T19:31:52.225Z, n8n 2.41.7, node v26.3.0, Windows_NT, command: node test/run-in-n8n.mjs
workflow.json sha256: af093802fa4a5083d6e4aad1c3d783bc97b13fd2410183661f414441b90270de

test/N8N-RUN.txt, lines 5 to 6

No internet and no real key used. "Claude: classify and draft" and "Failed runs (n8n API)" run as the real n8n nodes, but talk to local stand-ins on 127.0.0.1 (test/local-stand-ins.cjs). These nodes are replaced by stub Code nodes in the test copies only:
  Log decision, Slack: log write failed, Slack: approve reply, Email: send reply, Slack: reply to send by hand, Look up payment (read-only), Slack: escalate to team, Slack: urgent escalation, Email: alert the team, Slack: new sales lead, Send self-test, Wait for the test runs, Read last 24h log, Slack: morning summary, Email: problems to owner, Heartbeat ping.

test/N8N-RUN.txt, line 29

TOTAL: 18 passed, 0 failed
test/RESULT.txt, the full file (120 lines)
Kedgework demo build: support inbox triage. Not client work. Synthetic data.
Offline test run: 2026-10-05T19:31:58.427Z, node v26.3.0, command: node test/run-tests.mjs
No network and no API key used. The AI answers below are mock answers from fixtures/messages.json.
This test does NOT measure how accurately the real model classifies messages.

[workflow]
PASS  workflow.json is valid JSON  :: 42 nodes
PASS  has the n8n export fields (name, nodes, connections, settings)
PASS  workflow.json matches the sources (prompt, schema, answers, rule code)
PASS  node names and ids are unique
PASS  every node has type, typeVersion, position and parameters
PASS  every node type exists in n8n (list from the n8n-nodes-base package)  :: 16 types: stickyNote, webhook, set, code, respondToWebhook, removeDuplicates, httpRequest, googleSheets, slack, switch, noOp, if, emailSend, scheduleTrigger, wait, n8n
PASS  every typeVersion is one n8n ships for that node
PASS  every connection points to an existing node, with valid output indexes  :: 43 connections
PASS  every working node is reachable from a trigger (no orphans)
PASS  credentials are placeholders only, of a type n8n accepts on that node  :: 17 credential references, all REPLACE_ME
PASS  no secrets in the file (API keys, tokens, passwords)
PASS  Claude call: Anthropic Messages API, model, structured output, fallbacks
PASS  rule code inside the n8n Code nodes is the tested source
PASS  Code node glue compiles (syntax check of every Code node)  :: 7 Code nodes
PASS  Switch "Route" covers every route, selftest first, unknown routes go to a person
PASS  replies wait for Approve in Slack, then go out by email; chat and WhatsApp replies go to a person  :: buttons: Approve and send / Decline, answer by hand
PASS  a draft waits at most 4 hours for a click, then goes to the team  :: limit 4 hours; on timeout: rule approval_timeout, to #support-team
PASS  urgent and team alerts: 3 tries in Slack, then the same alert by email  :: retry 3 x 5 s, then Email: alert the team
PASS  every outside address is a placeholder (except the Anthropic API)  :: 4 web addresses: api.anthropic.com, REPLACE_ME_PAYMENTS_API_HOST, REPLACE_ME_N8N_HOST, REPLACE_ME_HEARTBEAT_URL
PASS  the webhook answers before the AI call, and a repeated message id is dropped  :: history up to 10000 ids, cleared every morning
PASS  a failed write to the log sheet is posted to Slack  :: channel #support-daily
PASS  log sheet stores values as text (cell format RAW)
PASS  privacy: n8n stores no successful runs; the payment check sends the email in a header, not in the URL
PASS  no fixed email addresses in the nodes: sender and owner are placeholders
PASS  morning check: schedule at 08:00, self-test, log, failed runs, summary, heartbeat  :: timezone America/Mexico_City, self-test read back from the log after 5 min
PASS  self-test messages in the workflow match the fixtures  :: fx01 expects auto_reply; fx05 expects human_urgent

[fixtures]
PASS  12 messages, all marked synthetic, unique ids
PASS  mock model outputs follow prompts/output-schema.json
PASS  schema enums match the rule code and the approved answers

[routing]
PASS  fx01 auto_reply (approved_answer)  :: answered from faq_fees
PASS  fx02 auto_reply (approved_answer)  :: answered from faq_countries, faq_limits
PASS  fx03 lookup (payment_lookup)  :: check payment PAY-48213 in the payments system
PASS  fx04 auto_reply (payment_no_reference)  :: answered from faq_need_reference
PASS  fx05 human_urgent (urgent_keyword)  :: message contains "did not make"
PASS  fx06 human_urgent (urgent_keyword)  :: message contains "abogado"
PASS  fx07 human (human_category)  :: category verification is always handled by a person
PASS  fx08 human (unsupported_number)  :: draft contains numbers not in the approved answers: 0.5
PASS  fx09 human (large_amount)  :: amount 4800 USD at or above 1000
PASS  fx10 auto_reply (approved_answer)  :: answered from faq_password_reset
PASS  fx11 forward_sales (sales_lead)  :: business enquiry
PASS  fx12 human (invalid_model_output)  :: model output unusable: reply cut off (max_tokens)
PASS  guardrails catch the two deliberately wrong AI answers  :: fx05 wrong category, fx08 made-up fee: both go to a person
PASS  no message about money at risk is ever answered automatically

[workflow]
PASS  Code nodes run end to end with a fake n8n context (fx01 approved and declined, fx03 lookup, fx05, morning)  :: fx01 -> auto_reply; fx01 approved -> send, declined -> person, no click -> person, Slack down -> email; fx03 -> lookup -> auto_reply; fx05 -> human_urgent; fx05 Slack down -> URGENT email; morning: self-test 2/2 from the log, FAIL when missing

[rules]
PASS  low confidence goes to a person
PASS  confidence threshold comes from Settings
PASS  third contact on the same issue goes to a person
PASS  angry customer goes to a person
PASS  asking for a person works in en, es, pt
PASS  urgent words in es and pt override the AI
PASS  dispute and technical categories go to a person
PASS  spam is logged only, sales go to sales
PASS  answer citing no approved answer, or an unknown one, goes to a person
PASS  number check reads 5.000, 5,000 and 0,5 correctly
PASS  large amount threshold also applies to account access
PASS  API failures, refusals and bad JSON go to a person
PASS  prompt injection in the message cannot get money moved or a reply sent by itself
PASS  message text cannot close the <message> wrapper in the prompt
PASS  lookup: completed, late, failed, not found, wrong owner, wrong reference
PASS  approval step: only an explicit Approve sends; Decline, no click in time or anything unclear goes to a person
PASS  urgent words still make a message urgent when the AI call failed  :: fx05 with the AI down -> human_urgent (message contains "did not make")
PASS  Slack refused an alert: the email names the message, the reason and the error, and marks urgent ones  :: URGENT support message: account_access (Slack alert failed)
PASS  the log row keeps no message text and no email address  :: 14 columns: logged_at, received_at, message_id, channel, language, category, route, rule, confidence, selftest, model, input_tokens, output_tokens, error

[morning]
PASS  the line shows the local time of the workflow, not UTC
PASS  normal day: counts the last 24 h, ignores self-tests and older rows  :: Morning check 2026-10-06 08:00 (America/Mexico_City): 12 messages in 24 h. 4 answered from approved answers, 1 checked against payments, 6 to people (2 urgent), 1 to sales, 0 ignored. Failed runs: 0. Self-test: 2/2 passed. Status: WARN.
PASS  self-test with the wrong route fails the check
PASS  workflow did not answer the self-test: fail
PASS  failed runs in the last 24 h fail the check, older ones are ignored
PASS  no messages at all: warning to check the inbox connection
PASS  self-test missing entirely: fail
PASS  n8n API could not be read (error item): fail, never a silent "0 failed runs"  :: could not read the failed runs from the n8n API: connect ECONNREFUSED 127.0.0.1:443
PASS  n8n API step passed other data through (no execution ids): fail
PASS  the log sheet could not be read: fail

[n8n]
PASS  every node parameter resolves against the real n8n node definitions (no unknown names, modes or choices)  :: n8n-nodes-base 2.41.5, 42 nodes, 0 problems
PASS  workflow.json was imported and run in a real n8n (test/N8N-RUN.txt matches this file)  :: n8n 2.41.7: 18 checks passed, 14 of them runs inside n8n

[house]
PASS  no em dash or en dash in any deliverable file  :: 23 files
PASS  prompts/categories.md documents every rule id used in the code  :: 26 rule ids
PASS  README carries the demo line
PASS  README matches the workflow: email-only sending, approval buttons, every Slack channel, the third-contact wording  :: 5 Slack channels listed

Routing table (12 synthetic fixtures; "model" is the mock AI answer, not a live call)
id   | truth             | model             | route         | rule                 | after lookup
fx01 | faq               | faq               | auto_reply    | approved_answer      | 
fx02 | faq               | faq               | auto_reply    | approved_answer      | 
fx03 | payment_status    | payment_status    | lookup        | payment_lookup       | auto_reply (lookup_on_time)
fx04 | payment_status    | payment_status    | auto_reply    | payment_no_reference | 
fx05 | fraud_security    | account_access    | human_urgent  | urgent_keyword       | 
fx06 | complaint_legal   | complaint_legal   | human_urgent  | urgent_keyword       | 
fx07 | verification      | verification      | human         | human_category       | 
fx08 | faq               | faq               | human         | unsupported_number   | 
fx09 | payment_status    | payment_status    | human         | large_amount         | 
fx10 | account_access    | account_access    | auto_reply    | approved_answer      | 
fx11 | sales_partnership | sales_partnership | forward_sales | sales_lead           | 
fx12 | payment_status    | (no usable output) | human         | invalid_model_output | 

Morning line on the sample log: Morning check 2026-10-06 08:00 (America/Mexico_City): 12 messages in 24 h. 4 answered from approved answers, 1 checked against payments, 6 to people (2 urgent), 1 to sales, 0 ignored. Failed runs: 0. Self-test: 2/2 passed. Status: WARN. | 1 message(s) where the AI answer was unusable (sent to a person)

System prompt: 5319 characters, roughly 1773 tokens (estimate at 3 characters per token; not a real token count).

TOTAL: 79 passed, 0 failed
test/N8N-RUN.txt, the full file (29 lines)
Kedgework demo build: support inbox triage. Not client work. Synthetic data.
Run in a real n8n: 2026-10-05T19:31:52.225Z, n8n 2.41.7, node v26.3.0, Windows_NT, command: node test/run-in-n8n.mjs
workflow.json sha256: af093802fa4a5083d6e4aad1c3d783bc97b13fd2410183661f414441b90270de
How n8n was installed on the test machine: n8n 2.41.7 from an npx download (npm cache), started with a small module loader hook (NODE_OPTIONS=--require ...). The download was incomplete for Node 26: no sqlite3 binary and a half-unpacked @smithy/core. The hook points "sqlite3" to a separate sqlite3 5.1.7 copy with its prebuilt binary and "@smithy/core" to the copy inside n8n-nodes-base. n8n's own code is not changed. A normal install (Docker image, n8n Cloud, or npm i -g n8n on Node 22 or 24) does not need it.
No internet and no real key used. "Claude: classify and draft" and "Failed runs (n8n API)" run as the real n8n nodes, but talk to local stand-ins on 127.0.0.1 (test/local-stand-ins.cjs). These nodes are replaced by stub Code nodes in the test copies only:
  Log decision, Slack: log write failed, Slack: approve reply, Email: send reply, Slack: reply to send by hand, Look up payment (read-only), Slack: escalate to team, Slack: urgent escalation, Email: alert the team, Slack: new sales lead, Send self-test, Wait for the test runs, Read last 24h log, Slack: morning summary, Email: problems to owner, Heartbeat ping.
A Manual Trigger and a "Test input" node replace the webhook and the 08:00 schedule (both switched off in the copies).
Every other node runs for real inside n8n. The import and export checks use workflow.json exactly as shipped. Short log: test/n8n-run.log.

PASS  workflow.json imports into n8n 2.41.7 as shipped  :: 11 s
PASS  export after import: same nodes, parameters, error handling, connections and settings (saved as test/n8n-export.json)  :: 42 nodes
PASS  n8n accepts the placeholder credential ids and types (7 dummy credentials imported)  :: httpHeaderAuth, googleSheetsOAuth2Api, slackApi, smtp, n8nApi
PASS  test copies import (13 workflows)  :: 6.1 s
PASS  in n8n: fx01 -> auto_reply -> Email: send reply  :: request seen by the stand-in: model claude-opus-5-5, structured output, effort low, fallbacks default, system prompt cached 1h, API key sent as a header. 12 real nodes ran: Settings, Normalize message, Respond to webhook, Skip repeated messages, Claude: classify and draft, Parse and apply rules, Route, Reply ready, Send mode?, Read approval, Approved?, Reply by email?
PASS  in n8n: fx02 -> auto_reply -> Slack: reply to send by hand  :: 12 real nodes ran: Settings, Normalize message, Respond to webhook, Skip repeated messages, Claude: classify and draft, Parse and apply rules, Route, Reply ready, Send mode?, Read approval, Approved?, Reply by email?
PASS  in n8n: fx01-declined -> auto_reply, draft declined in Slack -> Slack: escalate to team  :: Read approval: draft_declined. 11 real nodes ran: Settings, Normalize message, Respond to webhook, Skip repeated messages, Claude: classify and draft, Parse and apply rules, Route, Reply ready, Send mode?, Read approval, Approved?
PASS  in n8n: fx01-no-click -> auto_reply, no click in Slack before the limit -> Slack: escalate to team  :: Read approval: approval_timeout. 11 real nodes ran: Settings, Normalize message, Respond to webhook, Skip repeated messages, Claude: classify and draft, Parse and apply rules, Route, Reply ready, Send mode?, Read approval, Approved?
PASS  in n8n: fx03 -> lookup -> Email: send reply  :: 14 real nodes ran: Settings, Normalize message, Respond to webhook, Skip repeated messages, Claude: classify and draft, Parse and apply rules, Route, Decide after lookup, Reply or person?, Reply ready, Send mode?, Read approval, Approved?, Reply by email?
PASS  in n8n: fx05 -> human_urgent -> Slack: urgent escalation  :: 7 real nodes ran: Settings, Normalize message, Respond to webhook, Skip repeated messages, Claude: classify and draft, Parse and apply rules, Route
PASS  in n8n: fx05-ai-down -> human_urgent, AI API overloaded -> Slack: urgent escalation  :: AI API answered 529 to all 3 tries; error logged as "api error: 529 - "{\"type\":\"error\",\"error\":{\"type\":\"overloaded_error\",\"message\":\"Overloaded (local stand-in)\"}}"". 7 real nodes ran: Settings, Normalize message, Respond to webhook, Skip repeated messages, Claude: classify and draft, Parse and apply rules, Route
PASS  in n8n: fx05-slack-down -> human_urgent, Slack refuses the alert -> Email: alert the team  :: email subject "URGENT support message: account_access (Slack alert failed)", to REPLACE_ME_TEAM_ALERT_EMAIL. 8 real nodes ran: Settings, Normalize message, Respond to webhook, Skip repeated messages, Claude: classify and draft, Parse and apply rules, Route, Slack failed: prepare email
PASS  in n8n: fx11 -> forward_sales -> Slack: new sales lead  :: 7 real nodes ran: Settings, Normalize message, Respond to webhook, Skip repeated messages, Claude: classify and draft, Parse and apply rules, Route
PASS  in n8n: fx12 -> human -> Slack: escalate to team  :: 7 real nodes ran: Settings, Normalize message, Respond to webhook, Skip repeated messages, Claude: classify and draft, Parse and apply rules, Route
PASS  in n8n: the same message id a second time stops at "Skip repeated messages" (no AI call, no reply)  :: items passed on: 0; Claude node ran: no; requests to the AI stand-in: 0
PASS  in n8n: morning (n8n API stand-in lists no failed runs) -> WARN  :: Morning check 2026-10-05 13:31 (America/Mexico_City): 12 messages in 24 h. 4 answered from approved answers, 1 checked against payments, 6 to people (2 urgent), 1 to sales, 0 ignored. Failed runs: 0. Self-test: 2/2 passed. Status: WARN. | 1 message(s) where the AI answer was unusable (sent to a person)
PASS  in n8n: morning-apifail (n8n API stand-in lists one failed run 2 h ago and one 5 days ago) -> FAIL  :: Morning check 2026-10-05 13:31 (America/Mexico_City): 12 messages in 24 h. 4 answered from approved answers, 1 checked against payments, 6 to people (2 urgent), 1 to sales, 0 ignored. Failed runs: 1. Self-test: 2/2 passed. Status: FAIL. | 1 failed run(s) in n8n in the last 24 h | 1 message(s) where the AI answer was unusable (sent to a person)
PASS  in n8n: morning-apidown (n8n API stand-in answers 503) -> FAIL  :: Morning check 2026-10-05 13:31 (America/Mexico_City): 12 messages in 24 h. 4 answered from approved answers, 1 checked against payments, 6 to people (2 urgent), 1 to sales, 0 ignored. Failed runs: unknown. Self-test: 2/2 passed. Status: FAIL. | could not read the failed runs from the n8n API: Service unavailable - try again later or consider setting this node to retry automatically (in the node settings) | 1 message(s) where the AI answer was unusable (sent to a person) | item handed on by the real n8n API node, fields: error

TOTAL: 18 passed, 0 failed

Honest status

Live test with Claude

Not run yet. The tests above use hand-written stand-in answers. This one sends the 12 invented messages to the real Claude API, exactly as the workflow builds the request, runs each answer through the workflow's own rules, and compares the category, the route and the rule with the expected ones. Two expected routes come from stand-in answers that are wrong on purpose (an invented fee, and an answer cut off halfway), so for those two messages only the category is compared.

Monthly cost

Example: 50 messages a day, about 1,600 runs a month including the morning checks.

ItemEstimateSource
Claude Opus 5.5 (Anthropic API)about 20 to 40 USD4 USD per million input tokens, 20 USD per million output tokens, Anthropic pricing, checked 2026-10-05. About 2,000 tokens in and 300 to 500 out per message. Opus 5.5 always thinks a little before answering (the flow sets effort to low), billed as output, hence the upper end.
n8nfrom 20 EUR a month on n8n Cloud (Starter, 2,500 runs, billed yearly), or free if self-hostedn8n pricing, checked 2026-10-05
Google Sheets and Slackyour existing accounts

Caching, not counted above: the fixed instructions (about 1,800 tokens) are cached for an hour. The first message after a quiet hour writes them at twice the input price (8 USD per million, about 0.015 USD), the next ones read them at 0.20 USD per million. At 50 messages a day that part of the input costs about 2 USD a month instead of 11. At low volume, when most messages arrive more than an hour apart, each pays the write: about 0.007 USD more per message than without caching. Claude Sonnet 5.5 (2 and 10 USD per million, one field in the Settings node) would roughly halve the Claude cost. In the first week we measure the real token use and send you the number.

Download the workflow

Download workflow.jsonn8n workflow, 42 nodes, 168 KB

n8n workflow, import from file. Every credential and outside address is a placeholder named REPLACE_ME_... (only the Anthropic API address is real). Starts in draft mode (send_mode in the Settings node).

Same file as tested: its sha256 is af093802fa4a5083d6e4aad1c3d783bc97b13fd2410183661f414441b90270de, the value on line 3 of test/N8N-RUN.txt.

Download README.md (10 KB). Its links point to other files of the demo package (prompts/, fixtures/, src/, test/), which are not published here.

Full README

Rendered from README.md. Links to files that are not published here are shown as plain text.

Show the full README.md (110 lines)

AI support inbox triage

Demo build by Kedgework. Not client work. Synthetic data. ExamplePay is a fictional payments company. Every message in this folder was invented for the demo.

Diagram of the flow. 01: a message arrives by email, web chat or WhatsApp, through a webhook. 02: Claude reads it. 03: fixed rules decide between five outcomes: answer sent, payment checked, to your team, urgent to your team, to sales. 04: every decision is logged, then checked every morning at 08:00, your time: the log, a self-test with two test messages, failed runs, and one line in Slack.
How a message moves through the flow

The problem

A payments company gets the same questions all day: fees, limits, "where is my transfer". Mixed in are a few that cannot wait, like an unrecognised payment or a legal threat, and they wait behind the easy ones.

What the flow does

  1. A message arrives by email, web chat or WhatsApp. The flow confirms receipt to your inbox tool at once. A copy with the same message id is dropped until the next 08:00 check clears the list, so a quick retry from your tool gets no second answer; a resend after 08:00 counts as new.
  2. Claude reads it: topic, language (Spanish, Portuguese, English), mood, and a short draft reply, only when your approved answers cover the question.
  3. Fixed rules decide, not the AI. A person always gets the message when:
    • it mentions fraud, a payment the customer did not make, the police or a lawyer (marked urgent, even when the AI call fails)
    • it is about identity checks, refunds or disputes
    • a payment question involves 1,000 USD or more
    • the customer is angry, or your inbox tool reports a third message on the same issue
    • the AI is unsure, or its draft contains a number that is not in your approved answers
  4. Payment questions with a reference are checked in your payments system (read only) and answered from a fixed template, never from the AI.
  5. A person approves, then the reply goes out. For the first weeks every draft is posted in Slack with two buttons, Approve and send or Decline, answer by hand. Declined drafts, and drafts with no click within 4 hours, go to your team to answer by hand.
Mock-up of the approval message in the Slack channel #support-drafts: a reply waiting for approval for the invented message fx01, a question about the fee for an international transfer of 200 USD, the draft reply, and two buttons, Decline, answer by hand, and Approve and send. With no click within 4 hours it goes to #support-team.
Mock-up of the approval message, built with n8n's own Slack message code from the workflow's template and the invented message fx01. Not a screenshot of Slack.

In this demo, replies go out by email. For web chat and WhatsApp the approved reply is posted in Slack for a person to send, until we connect your chat provider. The payment check matches the sender's email with the payment owner, so there a payment question goes to a person unless your chat tool passes a verified email.

If Slack is down, alerts for urgent messages and for the team are tried 3 times, then sent by email to a team address you choose.

What you import into n8n

The workflow as the n8n editor draws it. Top row, triage: inbound message, settings, normalize message, respond to webhook, skip repeated messages, Claude classify and draft, parse and apply rules, then a route to the payment lookup, the Slack approval, escalations, the sales lead and the email steps. Bottom row, the morning check at 08:00: forget the message ids from yesterday, send self-test messages, wait for the test runs, read the last 24 hours of the log, failed runs from the n8n API, morning summary in Slack, email to the owner when problems are found, heartbeat ping. Red marks show credentials that are not connected yet.
workflow.json opened in a local n8n 2.41.7. The red marks mean "credential not connected yet": every credential is a placeholder until setup.

Checked every morning at 08:00, in your time zone

  • Two test messages go through the live flow. Five minutes later the log must show one answered and one flagged as urgent. Test messages never reach customers or your team.
  • Runs that failed in the last 24 hours, read from n8n. If that list or the log cannot be read, the check says FAIL instead of reporting zero.
  • How many messages were answered, checked, passed to people (and how many urgent), and how many AI answers were unusable. No messages at all means the inbox may be disconnected.
  • The list of message ids from the day before is cleared (n8n caps its size).

You get one line in Slack, problems also by email, and a ping to an outside monitor in case the check itself does not run.

What it costs to run each month

Example: 50 messages a day, about 1,600 runs a month including the morning checks.

ItemEstimateSource
Claude Opus 5.5 (Anthropic API)about 20 to 40 USD4 USD per million input tokens, 20 USD per million output tokens, Anthropic pricing, checked 2026-10-05. About 2,000 tokens in and 300 to 500 out per message. Opus 5.5 always thinks a little before answering (the flow sets effort to low), billed as output, hence the upper end.
n8nfrom 20 EUR a month on n8n Cloud (Starter, 2,500 runs, billed yearly), or free if self-hostedn8n pricing, checked 2026-10-05
Google Sheets and Slackyour existing accounts

Caching, not counted above: the fixed instructions (about 1,800 tokens) are cached for an hour. The first message after a quiet hour writes them at twice the input price (8 USD per million, about 0.015 USD), the next ones read them at 0.20 USD per million. At 50 messages a day that part of the input costs about 2 USD a month instead of 11. At low volume, when most messages arrive more than an hour apart, each pays the write: about 0.007 USD more per message than without caching. Claude Sonnet 5.5 (2 and 10 USD per million, one field in the Settings node) would roughly halve the Claude cost. In the first week we measure the real token use and send you the number.

What is stored, and where

  • The log sheet: category, route, rule, confidence, token counts and errors. No message text and no email address.
  • n8n keeps no copy of successful runs. Failed runs are kept so they can be fixed and may include the message; on a self-hosted n8n we delete them after 7 days, on n8n Cloud your plan's retention applies.
  • Slack messages and alert emails to your team include the sender and a short preview, so keep those channels private.
  • The payment check sends the customer's email in a request header, never in the web address.

What we would need from you

  • Where messages arrive today (mailbox, chat tool, WhatsApp provider), with a stable id per message (most tools send one).
  • 30 to 50 past messages with personal data removed, and how your team answered them.
  • Your approved answers (help articles or saved replies).
  • A read-only API key for payment lookups, or we start without lookups.
  • Five private Slack channels (#support-drafts, #support-team, #support-urgent, #sales, #support-daily) and a team email address for alerts when Slack is down.
  • An Anthropic API key and an n8n account in your name, so you see every bill. We never ask for passwords: an invite with the smallest permissions is enough.

Honest status

  • Tested offline: the workflow file and every node setting (against n8n 2.41's own node definitions), every rule on 12 invented messages, the approval limit, the Slack fallback and the morning check. test/RESULT.txt
  • Tested in a real n8n 2.41.7 on our machine, no internet: the file imports as shipped and exports back the same (test/n8n-export.json). Ten runs with invented messages reached the right place, including no click in time, the AI API overloaded (the fraud message stayed urgent) and Slack refusing an urgent alert (sent by email). The Claude and n8n API steps talked to local stand-ins; Slack, Google Sheets, email and payments were test stubs. A repeated message stopped at the repeat check, and the morning check caught a failed run and an unreadable n8n API. test/N8N-RUN.txt, test/n8n-run.log
  • Not tested yet: live Claude, Slack (buttons and the 4-hour limit in a running n8n), Google Sheets, email and payments. Claude's accuracy on real messages needs real messages and an API key.

Live test with Claude

Not run yet. The tests above use hand-written stand-in answers. This one sends the 12 invented messages to the real Claude API, exactly as the workflow builds the request, runs each answer through the workflow's own rules, and compares the category, the route and the rule with the expected ones. Two expected routes come from stand-in answers that are wrong on purpose (an invented fee, and an answer cut off halfway), so for those two messages only the category is compared.

Run it from the support-triage folder:

# Put your Anthropic API key in a local .env file that is never committed (ANTHROPIC_API_KEY=...).
node --env-file=.env test/live-eval.mjs --run --max-usd 1.00
  • Without --run it sends nothing. It shows the plan and the estimated cost: about 0.11 to 0.43 USD for the 12 messages, and up to 0.64 USD if every answer came from a fallback model.
  • --max-usd is a spending limit, 1.00 USD by default. Before each call the script sets aside the most one attempt of it can cost (the whole max_tokens at the fallback price, input counted high: up to 0.14 USD here) and stops if that would pass the limit. A call that times out or loses the connection counts as its full reservation and is not retried. The one way to go over: when Anthropic answers with a fallback model it can bill two attempts for one call, so the total can pass the limit by up to one reservation.
  • It writes test/LIVE-RESULT.txt (what matched, money spent, and for each message the model that answered and every attempt Anthropic reports) and test/live-responses.json (Claude's raw answers). The API key is never printed or written to a file.

For your technical team

PathWhat it is
workflow.jsonn8n workflow, import from file. Every credential and outside address is a placeholder named REPLACE_ME_... (only the Anthropic API address is real). Starts in draft mode (send_mode in the Settings node).
prompts/Instructions to Claude, approved answers, answer format, and the categories and rules in order (categories.md).
fixtures/12 invented messages with the expected result, and an invented log for the morning check.
src/The rules as plain JavaScript. build-workflow.mjs copies them into the workflow, so the tested code is the code that runs.
test/run-tests.mjsOffline test; RESULT.txt holds the last run.
test/live-eval.mjsThe live check against the real Claude API. Not run yet. Without --run it only shows the plan and the cost estimate.
test/run-in-n8n.mjsImports the workflow into a real n8n and runs test copies with n8n execute, using local-stand-ins.cjs on 127.0.0.1. Writes N8N-RUN.txt, n8n-run.log and n8n-export.json.
images/, test/render_images.py, test/approval-card.cjsThe two pictures above and how they were made.

Notes for the build: repeats are dropped by n8n's Remove Duplicates node (up to 10,000 ids, cleared every morning). The approval is the Slack node's "send and wait" with link buttons, so whoever clicks must reach your n8n address. Set execution pruning for failed runs. The log tab grows by about 1,600 rows a month and the morning check reads all of it; if that gets slow, start a new tab.