Lead Intake
Lead sorting in n8n
I run a working n8n automation on a rented server in the EU, and you can try it right away. It checks a message from a web form, looks up the company by its ID number (IČO) in the Czech ARES register, uses Claude to sort the message by type and priority, and drafts a reply for the team.
A live n8n demo that sorts customer messages with Claude.
How it works
The workflow connects several apps and cloud services through their APIs: the public REST API of the ARES register, Anthropic's Messages API, Google Sheets with a service account and, in the local build, Salesforce. The request to Claude is built from the message and the ARES data, and Claude's answer has to fit a fixed JSON schema. The result is stored in a table in n8n and also, without the message text, in two Google Sheets files. A scheduled job runs every hour, and once a day has ended it calls a webhook that links that day's decision records into a chain of SHA-256 hashes; the /overeni check page recomputes that chain right in the browser.
The logic is covered by 117 unit tests that run before every deployment. Twelve sample messages, including spam and a prompt injection attempt, go through the workflow on a local n8n instance, from the check and the ARES lookup to sorting by the fixed rules, with no AI call. A daily cap of 150 AI calls limits the cost. If Claude fails, gives an answer that does not fit the schema, or the cap is used up, the fixed rules sort the message instead. A separate error workflow catches failures. If saving to Google Sheets fails, the message is not lost and an alert row appears in n8n. The demo runs in Docker behind an nginx reverse proxy that makes only the demo's own pages reachable from the internet.
To measure the AI step, 40 made-up test messages with checked labels went through the workflow twice, once sorted by Claude Haiku and once by the fixed rules. Claude put all 40 in the right category and got both category and priority right for 36; the rules got the category right for 24, and category and priority together also for 24, and missed most spam, including a prompt injection they filed as an urgent complaint. One message cost about $0.0002. Claude Code wrote the messages for the test, to the same definitions the prompt uses, and the test ran once with no repeats, so real mail may score lower.
In the local build each stored message also becomes a Lead in a Salesforce Developer Edition org, created by n8n's Salesforce node. The Lead holds the receipt number, the firm and its address from ARES, the source, a status and a rating from the priority, but never the name, e-mail or message text. If Salesforce refuses a Lead, n8n adds an alert row and the rest of the run goes on. A Workato recipe (Free plan) watches the same org and gives every Hot web Lead a follow-up task due the next day; Leads with another rating start no job.
The demo's logic is written in JavaScript and the scheduled job in Python. AI coding agents (Claude Code) write the code in my projects under my direction; the brief, workflow design, test criteria, review and sign-off are mine.
Limits and decisions
- The customer never gets AI text. Claude writes only a reply draft for the team to review. The local build's confirmation e-mail to the customer is fixed text that never quotes the message, so the form cannot relay spam.
- The Gmail branch is not tested end to end. The local build can store messages in Google Sheets and send the e-mails through Gmail. That branch is built and its report logic has unit tests, but it has not run end to end yet, and none of it is on the public server.
- Salesforce stays in the local build. On the server it would need a privacy-notice passage naming Salesforce as a processor, a 7-day clean-up of the Leads, and a sign-in with a certificate instead of OAuth, because the OAuth sign-in's browser redirect cannot pass nginx.
- No message text in Google Sheets. Google keeps deleted rows in a file's version history for as long as the file exists, so the 7-day clean-up could not remove a message there. The sheets get the category, priority and receipt number, not the visitor's words. The demo also uses a free Google account, which has no data processing agreement; a client would use Google Workspace with Google's DPA.
- No CAPTCHA yet. The public server has per-address rate limits in nginx and a daily cap on AI calls. A CAPTCHA, and a per-address limit on the local build's customer confirmations, are not built; a company going live would need them.
What's inside
- The public form asks for a message, an optional company ID (IČO) and consent, never a name or…
- Claude Haiku sorts each message in one Messages API call that must answer in a fixed JSON schema…
- The visitor's text sits inside <inquiry> tags, is marked as data in the prompt and has its own…
- Costs are capped at 150 AI calls a day.
- Only the demo's own pages are reachable from the internet
- Every stored message also gets a receipt with its number and time, Claude or the rules (and why)…
Built with: n8n, Claude API, Salesforce, Workato, JavaScript, Python, Docker, Claude Code
Previous project: Portfolio HQ · Next project: Insurance Assistant · All projects