Automated triage for dental clinic enquiries. A patient fills in a web form; LeadFlow logs it, has an AI sort it, alerts staff on Telegram within seconds if it's an emergency (day or night), and emails the patient an acknowledgement. Built with self-hosted n8n, Google Gemini, Google Sheets, Gmail and Telegram.
A small clinic gets appointment requests, dental emergencies and insurance questions in one shared inbox, sorted by hand. Outside office hours nobody is watching it, so an emergency sent at 11 pm can sit unread until morning.
flowchart LR
A[Patient web form] --> B[Clinic settings]
B --> C{Gemini classifies}
C -- fails --> D{Backup Gemini call}
D -- fails --> E[Safety net:<br/>needs review]
C --> F[Build record]
D --> F
E --> F
F --> G[(Google Sheet)]
G --> H{Spam?}
H -- yes --> I[Stop]
H -- no --> J[Office-hours check]
J --> K{Route by category}
K -- emergency / needs review --> L[Telegram alert to staff]
K -- appointment / insurance / general --> M[Acknowledgement email]
L --> M
M --> N[Mark alert and email<br/>as sent in the Sheet]
Every enquiry is written to the Sheet before any alert or email is sent, so nothing is lost if a later step fails.
| The AI decides | What happens |
|---|---|
| Emergency | Telegram alert to staff immediately, at any hour; patient email with "if this is life-threatening, call 911" |
| Appointment / Insurance / General | Patient email saying what happens next, worded for whether the clinic is open or closed |
| AI couldn't classify it | Logged as general / high / fallback; staff alerted to review it by hand |
| Spam | Logged and flagged; no alert, no email |
- Three layers so no enquiry is dropped. Main AI call (3 retries), then a backup AI call (5 retries), then a safety net that labels it for a person to review. Each layer was tested by deliberately breaking the one before it.
- The AI errs towards safety. When unsure, a message is not spam and a possible emergency is an emergency. A false alarm costs a minute; a missed emergency costs a patient.
- The dropdown is a hint, not the answer. A patient who picks "Book an appointment" but describes a swollen jaw and fever is routed as an emergency.
- Prompt-injection guard. Patient text is treated as data, so "ignore your instructions" inside a message has no effect.
- Data minimisation. Name, email and phone are never sent to the AI; it only needs the message to classify.
- Strict output. A JSON schema forces the AI to answer with one of the allowed categories, or the step fails into the backup path.
- Safe with untrusted text. Sheet values are written raw (no formula injection); Telegram alerts escape
<,>and&so a patient's message can never break the alert. - Bots filtered at the door. The form rejects obvious scripts before they cost an AI call.
Full prompt and reasoning: prompts/classify-enquiry.md.
13 made-up enquiries in test-data/sample-enquiries.json were sent through the live form and checked against the expected answers: plain bookings and questions, emergencies hidden behind the wrong dropdown, SEO spam, gibberish, a genuine patient message containing a link, a prompt injection, and a message in Taglish.
Result: 13/13 passed, every one classified by the main AI in 3–13 seconds.
python scripts/run_samples.py send you@gmail.com
python scripts/run_samples.py check "path/to/downloaded-sheet.csv"| Staff alert on Telegram | Patient email | The Sheet |
|---|---|---|
![]() |
![]() |
![]() |
- Copy
.env.exampleto.envand fill in an encryption key and your timezone. docker compose up -d, then open http://localhost:5678.- Create a Google Sheet with the header row from
templates/sheet-columns.csv. - Import
workflows/leadflow-enquiry-triage.json, connect the four credentials, and replaceYOUR_GOOGLE_SHEET_IDandYOUR_TELEGRAM_CHAT_ID. - Publish, and open the form at http://localhost:5678/form/brightside-enquiry.
Step-by-step guide: docs/setup.md.
- Demo only. This hasn't been reviewed for handling real health data, and the Gemini free tier may use submitted content to improve Google's products. A real clinic needs the paid tier, or another provider with a data-processing agreement.
- Same-model backup. On the free tier only one Gemini model answered reliably, so the backup retries that model. With a budget, the backup should be a different model or provider.
- Runs on localhost. A real deployment needs a server with HTTPS and a public form URL.
- No duplicate detection. A patient who submits twice creates two rows and, for emergencies, two alerts.
- A Google Sheets outage stops the run after retries, before the alert goes out. The run is kept in n8n's history; an n8n error workflow that alerts staff on any failure is the next improvement.
Brightside Dental is a fictional clinic. All data in this repository is made up.



