The same pipeline, as an n8n workflow
Clients post both kinds of jobs, so the triage ships twice. The pages you have been clicking run on TypeScript. This is the same flow rebuilt from n8n nodes: same weights, same prompts, same reply policy, and a test that runs its Code nodes against the TypeScript implementation.
inbound-triage.workflow.json · 21 nodes · credentials: Gmail, header auth, HubSpot, Slack · no keys inside
Import it in four steps
- 1In n8n, open Workflows and choose Import from file. Pick the downloaded JSON.
- 2Select your credentials on the nodes with a red badge. The file holds placeholders only.
- 3Edit the settings at the top of the Normalize message node: model endpoint, approval threshold, booking link.
- 4Set the Slack channel id on the two Slack nodes, set this workflow as its own Error workflow, and activate it.
The full guide, with the credential scopes and a curl example, is n8n/README.md in the repository.
Which node does what
Each row pairs a pipeline step with its n8n nodes and the TypeScript that does the same job. Open a row to see what matches and what does not.
| Pipeline step | n8n nodes | TypeScript source |
|---|---|---|
Normalize Matches and differsMatches. Web form and email payloads, the same length limits, one message shape. The model sees the sender as a name and a domain, never the address. Differs. No fixture channel. The webhook is protected by a shared header secret instead of the optional HMAC signature, and there is no rate limit or token budget.
|
| |
Classify Matches and differsMatches. The same system prompt (copied in by the build script), the same JSON schema, temperature 0. Differs. Any OpenAI-compatible chat completions API instead of the AI SDK. Retries are n8n retry-on-fail: four tries three seconds apart, not 1, 4 and 9 seconds.
|
| |
Score Matches and differsMatches. The same weights, bands and text rules. A test scores all twenty fixtures through both implementations and compares every factor. Spam and recruiting end the run as archived. Differs. The TypeScript app skips scoring for archived intents; here the Code node scores them as 0 and the If node routes them out.
|
| |
Enrich Matches and differsMatches. Company name guessed from the email domain, free-mail domains left blank. Differs. No homepage fetch. Add an HTTP Request node before it if you want the page title.
|
| |
CRM upsert Matches and differsMatches. Contact keyed on email, deal named "Triage <run id>: ...", the same stage per band, the same note text. Differs. The deal is created on every execution (the TypeScript adapter searches by name first), and the note is the deal description rather than a separate note object.
|
| |
Approval Matches and differsMatches. Scores at or above the threshold are approved by policy. Otherwise a Block Kit card with Approve and Reject buttons is posted and the run waits. Differs. The buttons are link buttons that open the execution resume URL, so there is no interactivity endpoint and no Slack signature check, anyone who can see the message can decide, and the reviewer is not recorded. No decision within 24 hours counts as a rejection.
|
| |
Draft reply Matches and differsMatches. The same voice guide, per-language sign-offs, booking token, and the same policy checks (no prices, links, addresses, phone numbers or lists; 40 to 220 words). Differs. One draft per execution. A policy failure stops the run and raises the alert; the TypeScript app redrafts up to three times first.
|
| |
Send Matches and differsMatches. Plain-text reply to the sender, signed by the agency. Differs. Not deduplicated per run: re-running the Send node from the editor sends a second mail.
|
| |
Errors and retries Matches and differsMatches. A failure is recorded with the step that failed and the reason, and a person is told. Differs. n8n keeps the failed execution and offers a retry for the whole execution in the editor. There is no step picker like the Retry from step button, and no dead-letter table.
|
|
When to choose n8n, and when to choose code
Choose n8n when
- The team already runs n8n and wants to change steps on a canvas.
- Volume is modest and a failed run can be replayed by hand.
- You want HubSpot, Slack and Gmail wired without writing adapters.
Choose code when
- You need a run log per step, retry from any step and a dead-letter state.
- Sends must be idempotent and every decision needs a test around it.
- The flow has to live in the same repository, review process and deploy as the product.
Either way the spec is the same: classify, score, enrich, file in the CRM, ask a person when the score does not clear policy, answer in the agency voice. The choice is about who maintains it and how much of the run you need to see afterwards.
What was checked: every node's parameters against the node definitions in n8n-nodes-base 2.15.1, and every Code node against the TypeScript pipeline on the twenty sample messages. The workflow has not been run on a live n8n instance from this repository, so expect to adjust a field or two against your own HubSpot portal.