Website, listing and telephony for a switchboard manufacturer: from an empty search page to the first call
A manufacturer of switchboards and low-voltage switchgear assemblies in Kaliningrad ran on referrals: no website, no map listings, and the office number printed on its certificates went unanswered. Between mid-August and early September 2026 the company got the full set: an Astro website, a Yandex Business listing, a cloud PBX with a voice menu, a GigaChat assistant, structured data, security headers, verified backups and a daily digest.
Buyers heard about the company from design engineers; online, it did not exist
A production shop in Kaliningrad: main and input distribution boards, automatic transfer switches, metering and lighting boards, automation cabinets, all built to the customer's single-line diagram. Two valid EAEU certificates of conformity, its own technical specifications, a passport for every unit. Orders came by referral and from design engineers.
A procurement manager who heard the name typed it into a search engine and found nothing: no website, no listings on maps or in directories. Registry aggregators carried phone numbers five years old. The top result for switchboard manufacturing in the city belonged to a company from another region.
The office landline was printed on certificates and product passports, paid for every month, and nobody sat next to it. The first cloud PBX setup produced an inbound call that dropped after 0 seconds with no ring tone: the caller heard silence, then a disconnect. The cause was in a user card, covered below.
One package: website, listing, phone
Every part rests on a single file of confirmed facts: a number reaches the site, the listing or the chat corpus only from a document or from the owner's own words. The rule went into the plan before the first line of copy: a corporate buyer checks a supplier against the registries in a minute, and one discrepancy wipes out trust in the whole page.
A single file of confirmed facts; the owner's 38-minute voice review transcribed locally, with no outside services
Astro 5, eight pages, two builds: own domain and a demo subdirectory; a form that takes the single-line diagram as an upload
A service on the server: the request goes to disk first, then to Telegram; consent and attachment type checked server-side
Rostelecom cloud PBX: a three-department voice menu, ring-through to mobiles, the office number moved into the PBX
Yandex Business listing, LocalBusiness markup, IndexNow, security headers, verified backups, an evening digest
Numbers from documents only
The owner walked through the draft by voice: a 38-minute recording, transcribed locally on my own machine because it is an internal conversation about the business. Checking the site against that review on 36 points gave 33 matches; one discrepancy was substantive: the products page promised to pick components without a single-line diagram; in the owner's words, no board gets built without one. The paragraph was rewritten. The first version carried 32 "to be confirmed" placeholders; by the end of the edits none were left.
The headline figure on the home page, 6300 A, comes from the certificate and the company's own technical specifications; the products page places the everyday production range of up to 630 A next to it, with an explanation of where the design boundary lies. The section on shipping to the rest of Russia was written after reading the primary sources: the region is a special economic zone, cargo leaving it passes customs control, and for the buyer it is an ordinary domestic delivery. A shipment figure mentioned in conversation was taken off the site until confirmed. A separate pass rewrote 31 contrastive constructions into plain statements and cut repeated sentences between pages from 16 to 4.
Two builds and three indexing blocks
The site runs on Astro 5 and Tailwind 4, static, eight pages, with its own graphics: 16 icons on a shared grid and a single-line diagram of a typical board with plausible data that belongs to nobody, because a scan of a real diagram shows the customer's project in the title block. Before the domain existed, the site was built as a subdirectory of a domain already running with a certificate, so the owner could view the demo over https without a DNS record. The link checker catches both a dead link and a link missing the prefix: the logo linked to "/", the home page of the host domain, and a plain "target exists" check saw nothing wrong.
Until the content was approved, the site was hidden from search engines three ways at once: noindex in the markup, the X-Robots-Tag header and robots.txt. All three had to come off together: the header outranks the meta tag and would have kept the site out of the index on its own. The flag lives in the deploy script; otherwise the next deploy would have closed the site again. DNS caching went from a day down to an hour: if the site moves to a standby server, visitors pick up the new address within the hour.
A form that does not lose the diagram
In this trade a request almost always comes with a single-line diagram. The request is written to disk first and only then sent to Telegram; attachments reach the managers' group as separate messages, up to 5 files. The limits of 20 MB per file and 60 MB per request are stated on the page with the same numbers the service enforces. Checks that lived only in the browser moved to the server: consent to data processing, file type, a hidden trap field and fill-in time; a submission faster than two seconds was typed by a machine and is dropped silently.
Failure used to look like success: on any error the visitor landed on the thank-you page. Now the page shows the reason and the phone numbers; a file that failed validation reaches the manager as a separate "attachments not accepted" block. The Telegram inbox was losing enquiries: text went to the group as HTML without escaping, and a less-than sign in an everyday phrase like "input < 630 A" killed the whole message, with the sender getting an automatic "received" reply. Escaping is in place, and the log now records the body of Telegram's response.
Telephony: why inbound calls dropped at 0 seconds
The Rostelecom cloud PBX was connected and the routing rule was sound: active, around the clock, to the group. An inbound call reached the line and dropped at once; the call log showed a single step, "call to the inbound line". The cause was in the card of the only user: the mobile in the call scenario was switched off with a toggle, and no SIP device was attached, so the PBX had nowhere to ring. In the scenario diagram a disabled leg differs only by a grey frame, so the card of every number has to be checked individually. The domain time zone was corrected along the way (the call log had been an hour off), a default outbound number was set, and voicemail notifications, which stood at "do not send", were switched on.
The structure was built: three users within the plan — management, accounting, technical department — each with a mobile; the line count went from 2/2 to 6/6. The voice menu is synthesised, 13.6 s: a digit sends the call to a department, and the timeout sends it to management. Two stock recordings enabled by default in the domain were switched off: the caller had been hearing someone else's text after our menu and again after 40 seconds of ringing. The logs of three real calls showed the routing works: a caller who pressed 1 at second 11 was connected to management; two callers hung up themselves at seconds 16 and 24. The problem was time: 13.6 s of menu plus 8 s of timeout made 22 s to the first ring, 8 of them silence. The timeout went down to 6 s, so the first ring now comes at 19.6 s; the only remaining lever is the menu text itself.
The office number printed on the certificates was absent from the PBX domain: test calls showed that calls to it never appeared in the log. The request to the operator was rewritten from those facts: three requirements and a list of what was already configured on our side; the transfer was completed on 1 September against a ticket raised on 28 August. The transferred number arrived with its own rule, every call straight to the group and past the voice menu; the rule was switched by hand, and both numbers now lead into the same menu. During the transfer the site showed the number issued by the PBX as the main one; afterwards the office number went back into the header, the call button and the markup, and the interim number was removed from all seven pages of the live site.
GigaChat assistant: built and tested, still waiting to be switched on at the customer's site
It answers questions about switchboards and LV assemblies and, where it has no answer, collects a request with a contact. The engine is FastAPI with streamed replies; the chat window uses no libraries. The corpus, about 3,200 tokens, fits entirely into the system prompt, so there is no vector search on purpose: search over that volume would add a point of failure and a chance of missing the right fragment. Half of the rules list what the bot does not know: prices, lead times, warranty, maximum current, component brands, past projects, certificate numbers. On each of these it says what exactly a manager will clarify and offers to take a phone number. Tested with a question on every item: it invented nothing; asked about a licence, it explained that assembling LV switchgear is not a licensed activity and named the technical regulations TR CU 004/2011 and 020/2011.
A request goes to disk first, then to the messenger, and carries the whole conversation. The Russian state root certificate, without which TLS to Sber fails, is passed as a parameter of that one connection: a state root installed system-wide could sign any domain for this machine. The rate limiter read the address from a header the client itself supplied: 25 requests with made-up addresses passed without a single refusal, each a paid model call on the owner's account. The limit moved into nginx, keyed on the socket address, at 20 requests per minute; the same 25 requests now hit a refusal. Services came off root: the chat had been running with the full set of 41 kernel capabilities; secrets are passed through EnvironmentFile, and request and transcript files got mode 640 in 750 directories.
The chat window is not connected to the customer's site: the site content is still being approved by the owner, and a bot that answers briskly from an unapproved description of services is worse than no bot. Five steps remain before switching it on: content approval, a bot and a managers' group for requests, a block in the nginx config, one line in the layout and a paragraph about the chat in the privacy policy.
Listing, markup and indexing
The Yandex Business listing was created with the same data as the site: weekday hours of 9:00–18:00, named by the owner, are identical in the listing and in the markup, because the listing and the site sit side by side in search results, and different answers to the same question read as neglect. The markup had been Organization, a legal entity with no place on the map; a LocalBusiness type was added with hours, a service area of "Russia", a link to the listing and coordinates. The link ties the site and the listing together in both directions; without it a search engine sees two different companies with the same name. The number taken from the listing page in Maps turned out to be the centre of the visible map area, two kilometres off; the coordinates were verified by forward geocoding of the building and reverse geocoding of the point.
Indexing was opened on the owner's decision, together with a Yandex Metrica counter with session replay and ownership verification in Yandex Webmaster and Search Console. By 25 August Google held six pages in its index; Yandex arrived for the first time on 27 August. The deploy announces changes itself over IndexNow: the URLs are taken from the sitemap, and the first call returned 202 for seven addresses. On 6 September Webmaster showed "links in file: 1" for the sitemap and zero pages in search: the URLs sit in the nested file of a two-level sitemap, and the nginx log showed the crawler fetching the index nine times without following it to the nested file. A second Sitemap line pointing straight at the file with the URLs went into robots.txt, and seven addresses were submitted for recrawl.
- A favicon served as four files: the Webmaster crawler asked for /favicon.ico and got a 404, even though the SVG was served
- A 1200×630 link preview from a workshop photo for messengers, and a custom 404 page with sections and phone numbers; the response code stays 404
- Meta descriptions cut from 174 and 176 characters to 132 and 131: search engines show about 160, and the ends of sentences were trailing off into ellipses
Server: headers, permissions, backups
add_header in nginx is not inherited downward once a nested block has one of its own: CSS, fonts and images were served with zero security headers. The set moved into a separate file included in every block; static files now carry four headers, nosniff among them; the server version is hidden, TLS 1.0 and 1.1 are off. The content security policy sat in a file of a format nginx does not read, so it was never sent; it is now set in the config, and the stray file, which had been served with a 200, is closed. The services had no memory ceiling: on a 1.9 GB machine a few simultaneous uploads could take all the memory and bring down nginx, which fronts both sites. Ceilings of 384 MB for the form and the chat and 192 MB for the inbox are set; nginx restarts 5 seconds after a crash. The server configuration is mirrored into the repository in full: the copy had fallen 25 lines behind production.
Backups did not cover the project at all. They now include requests, attachments, the chat log and a snapshot of the knowledge base; secrets are deliberately left out, since they can be reissued in a minute. The check used to look at size only; a transfer cut off midway produces a file of the right shape and impressive weight that will not unpack. Unpacking and a check that the requests are inside were added; a rehearsal restore gave 22 files with all three request files in place. A few days later the check began raising false alarms on sound archives: grep -q closed the pipe after the first match, tar received SIGPIPE, and pipefail counted the whole pipeline as failed — the sooner the requests were found, the more reliably the alarm fired. The backups are pulled by a second machine over a key with a forced command; the same machine checks both sites every five minutes. Each of its logins is recorded in the auth log, and if there has been no login for more than 26 hours the daily digest says so in Telegram. The line appears only when something is wrong: the two machines watch each other with no outside services.
An evening digest that measures
Every evening a digest goes to Telegram: requests for the day, how many people visited and how many came from search. The report had a hard-coded line, "the site is closed to search engines", true until 19 August and printed every evening after that; visitors are now counted from the nginx log on every run, and a line like that stays current. The first count filtered robots by agent name and reported 30 people in a day against two real ones: scanners have long presented themselves as an ordinary browser. Behaviour works: only a client that actually renders the page requests the font. The new count gives 29 people a week against 26 in Yandex Metrica; the three-person gap is the accuracy of the method, and before the fix the gap was 178. The digest text says "approximately" and points to Metrica for the exact figure.
Mobile layout and page weight
At 375 px the first screen of three pages ran off the right edge: a grid item defaults to being no narrower than its content, and the track holding the single-line diagram swelled to 586 px; after the fix the column is 335 px and the diagram scrolls inside its own container. Production photos got WebP and a fallback JPEG at 1600 and 800 px: the seven frames on the production page went from 3,200 KB to 1,168 KB on desktop and 415 KB on a phone, with no loss of resolution; the main product photo went from 627 to 147 KB on a phone. Ten Lighthouse runs after dropping the priority of a photo below the fold: LCP 2.40 → 2.10 s, page weight 367 → 324 KB, zero layout shift. Typography is part of the build, with 830 non-breaking spaces placed by a script; contrast by the WCAG formula is 17.8:1 for text and 8.0:1 for the accent. The main phone number was rendered 5–8 times per page; the home page went from 8 to 6 occurrences, and the full list of numbers is rendered once.
A static site, one machine and the operators' services
Static build, two variants for the own domain and a demo subdirectory, link checking and typography inside the build
Security headers on all static files, content security policy, rate limiting, services off root, restart after a crash
Lead intake with attachments and the chat engine: disk first, then Telegram; consent and file-type checks on the server
Chat assistant with a corpus of about 3,200 tokens in the system prompt and a list of what it does not know
Three-department voice menu, ring-through to mobiles, the office number moved into the PBX domain
A listing with the same hours and phone, a counter with session replay, LocalBusiness markup, search engines notified on deploy
Measurements before and after
to the first ring: 13.6 s of voice menu and 6 s of timeout; a caller who presses a digit connects sooner
seven frames on the production page; 1,168 KB on desktop, 1600 px resolution kept
ten Lighthouse runs; page weight 367 → 324 KB, zero layout shift
Filtering robots by agent name let through scanners posing as an ordinary browser: 30 people in a day against two real ones. Filtering by behaviour, the font request, gave 29 people a week against 26 in Metrica. The digest says "approximately": the log gives the order of magnitude, the exact figure lives in Metrica.
The site runs on its own domain, eight pages; the form takes requests with diagrams; the Yandex Business listing is live; the office number from the certificates answers with a voice menu and rings through to the staff's mobiles. By 25 August Google held six pages in its index and Yandex arrived on 27 August; after the sitemap fix on 6 September seven addresses went in for recrawl. A buyer who types the company name now finds the site, the listing and a phone that answers.
The chat assistant is built and tested on the questions it has no answer to: it invented nothing. It is not yet enabled on the customer's site, and that is deliberate: the owner approves the content first, and the bot answers from that.
An honest limitation: the whole production site sits on one machine at one facility. On 18 August a telecom outage at that facility took it down for several hours, together with a neighbouring server the changes never touched. That is why DNS caching is down to an hour and the backups go to a second machine and are verified by unpacking: a move to a standby server would take an hour on the visitors' side.
Between 18 August and 6 September the work log holds 92 entries, and each one ends with a check on the live site: page response codes, headers, layout at 375 px, an end-to-end request with an attachment, real calls traced through the PBX logs.
Where else the same package fits
Behind the case sits the standard task of "a working business's first presence online": website, listing and phone assembled in one pass and resting on one file of facts.
- → A workshop or plant with no website — a buyer checks a supplier against the registries in a minute; a site, a listing and full company details close that check
- → A landline nobody sits next to — a cloud PBX with a departmental menu and ring-through to mobiles, verified against the logs of real calls
- → A site exists, requests get lost — intake to disk before the messenger, attachments as separate messages, the reason for a refusal visible to both the visitor and the manager
- → Listing and site disagree — hours, phone and address from one source, LocalBusiness markup with a link to the listing
- → A chat assistant for a narrow niche — a fact corpus that fits in the system prompt, with half the rules listing what the bot does not know
- A single facts file: a number reaches the site, the listing and the chat corpus only from a document or the owner's words
- Lead intake "disk first, messenger second", with attachments as separate messages and server-side checks
- Backup verification by unpacking and by the presence of requests; two machines watching each other through the auth log
- A cloud PBX checklist: every number's card, the time zone, stock recordings, time to first ring measured from the logs
- An evening digest that measures state on every run and prints the backup line only when something is wrong
If a plant has no website and nobody answers the office line — that comes together as one package
We start from a facts file: what the documents and the owner's own words confirm. The site, the listing and the telephony are then built on it, and every change is checked on the live site and with real calls.
Related cases
Marketing automation platform: desktop → Telegram → web app
Grew from a Tkinter desktop app into a Telegram bot and a web app on Next.js + FastAPI + PostgreSQL + Celery…
Swift/SwiftUI MVP in 9 weeks — from kickoff to App Store
Native iOS 17+: schedule + push (24h/1h/10min), Speech Framework for on-device pronunciation, StoreKit 2 +…
PWA for a fitness studio: 1.2 MB vs 47 MB native
PWA with iOS Add to Home Screen, offline via Service Worker, WebPush iOS 16.4+, YClients embed + YooKassa…
The 9,900 ₽ audit — with a concrete report and quote
I'll tell you what to deploy in your business first, what the payback looks like, and whether you need AI for the task at all (sometimes you don't).
Or just send your question — I reply within 2 hours