Demo project
Website leads delivered to Telegram
A lead delivery demo: the landing form and the WordPress form both arrive in Telegram, with every delivery attempt in the journal.
integration development · Node.js · JavaScript · Telegram Bot API
in progressLanding entry crashes on a malformed request, ports hard-...

The task
A web lead has a shelf life measured in minutes. While it sits in an inbox or in the site’s admin panel, the customer is already writing to a competitor. The owner wants one thing: the name and the phone number landing where they already look all day - Telegram - and a way to tell whether the message actually arrived.
This is a working demo, not a client project. The companies are invented: ChistoDom, on-site cleaning of upholstery and carpets, and Volokno, a joinery workshop. The job itself is real and ordinary. The demo exists so lead delivery can be shown live instead of described.
The solution
Two entry points sharing one delivery module.
The first serves a finished demo landing page and handles its form. The landing files are read from disk untouched, and the sending script is injected into the HTML on the fly, right before the closing tag. Not a single line of the demo changes. One caveat worth stating plainly: the demo itself says its forms send nothing anywhere, and in the published demo that is still true. In this demo, sending is switched on deliberately - otherwise there would be nothing to show.
The second entry point is an intake for leads from external sites. It renders nothing and listens on one address: a lead arrives as structured data, gets checked, goes out to Telegram and lands in a journal. This is what the form on the WordPress site Volokno talks to.
Delivery is shared by both: up to three attempts with a growing pause between them. A lead becomes one journal line, with the attempts listed inside it: attempt number, Telegram’s response code, success or not. One real failure survives in the landing journal: three attempts rejected by Telegram in a row, the lead undelivered. It was left in place, because that is exactly what an undelivered lead looks like.
Details that are easy to miss
- The intake’s answer is a contract for the sending site, not a polite thank-you. Delivered, and the site closes the lead; not delivered, and it queues the lead and retries on a schedule. The lead stays on the site’s side and goes out again instead of disappearing with the failed request.
- At the intake, consent to personal data processing is a required field, not a decorative checkbox: without it the lead is rejected. The landing entry has no such check on the server yet, only in the browser.
- The intake journals rejections too, with the reason. Otherwise “no leads today” and “there were leads and we dropped them” look identical.
- The chat identifier never reaches the journals, because it must not appear in screenshots. The lead’s own content is stored there: this is a delivery journal, not anonymous telemetry.
- At the intake every field is trimmed to a length limit and the request size is capped: a public address must not become a way to fill up a disk. Serving the landing files separately checks that a path cannot escape the project directory.
- The form script intercepts submission, but validates the fields before intercepting: if they fail, the event goes back to the form’s own logic instead of being swallowed.
What is still open
The demo works and can be filmed, but it is not finished. The honest list.
The landing form never shows the visitor a failure. The script draws the “lead sent” card only on a successful answer; on a failed delivery or an unreachable server it does nothing at all - precisely the three-rejections scenario described above. It needs an error branch.
The first entry point reads the request body with no size limit and no handling for a parse error: malformed JSON takes the whole process down. The intake does the same job correctly; the landing entry does not.
The form script looks for the comment field under the names task and comment, while the landing form calls it what: the “what to clean” text never reaches Telegram.
Ports are hard-coded - 4319 for the landing, 4320 for the intake - and 4320 is the same port a neighbouring setup uses, so the two cannot run at once. The demo reads no configuration from the environment at all. It has no README of its own, the description lives in the shared catalogue of demos, and there is no self-check script like the neighbouring setups have, only live runs. And the bot keys still come from an adjacent project: this demo needs its own Telegram bot under a neutral name, and only the account owner can create it.
Screenshots



