Skip to content

Allow this site to count your visits with Yandex Metrica? Metrica will store cookies in your browser to do this. How the site uses cookies

VC
Case 18 of 22 · Manufacturing · Online presence and telephony

Website, map listing and phone line for a switchboard manufacturer: from nothing in search to the first call

A manufacturer of switchboards and low-voltage switchgear in Kaliningrad worked on referrals. It had no website and no map listings, and nobody answered the office number printed on its certificates. Between mid-August and early September 2026 I built it a website with Astro and a chat assistant on GigaChat (Sber's language model), set up a Yandex Business listing and a cloud phone system (PBX) with a voice menu, and added structured data for search engines, security headers, verified backups and a daily summary.

Industry
Switchboard and LV switchgear manufacturer, Kaliningrad
Technology
Astro 5 · nginx · Rostelecom cloud PBX · GigaChat
Timeline
Mid-August to early September 2026
Outcome
Call to the office: dropped at once → first ring at 19.6 s
01 · Problem

Buyers heard of the company from design engineers, and online it didn't exist

A production shop in Kaliningrad builds main and input distribution boards, automatic transfer switch panels, metering and lighting boards and automation cabinets, all to the customer's single-line diagram. It holds two valid EAEU certificates of conformity and its own technical specifications, and every unit ships with a passport. Orders came through referrals and 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. Business-registry sites showed phone numbers from five years ago. The top result for switchboard manufacturing in the city belonged to a company from another region.

The office landline was printed on the certificates and product passports and paid for every month, and nobody was there to answer it. After the first cloud PBX setup, an incoming call dropped at once with no ringing: the caller heard silence, and then the line went dead. The cause was a setting in a user's card, described below.

02 · Solution

One project: website, listing and phone

Every part rests on one file of confirmed facts. A number goes onto the site, the listing or the chat's knowledge base only if it comes from a document or from the owner's own words. This rule was set before the first line of text was written. A corporate buyer checks a supplier against official registries in a minute, and a single mismatch undermines trust in the whole page.

01
Facts

One file of confirmed facts; the owner's 38-minute voice review transcribed on my own computer, with no outside services

02
Website

Astro 5, eight pages, two builds: one for the company's domain and one as a demo in a subfolder; the form accepts the single-line diagram as a file

03
Requests

A service on the server saves each request to disk first, then sends it to Telegram; consent and file type are checked on the server

04
Telephony

Rostelecom cloud PBX: a voice menu for three departments, calls forwarded to mobiles, the office number moved into the PBX

05
Search and monitoring

Yandex Business listing, LocalBusiness markup, IndexNow, security headers, verified backups, an evening summary

Numbers from documents only

The owner reviewed the draft by voice: a 38-minute recording, transcribed on my own computer because it is an internal conversation about the business. Checking the site against that review on 36 points gave 33 matches. One of the mismatches mattered: the products page promised to choose components without a single-line diagram, and the owner said no board is built without one. The paragraph was rewritten. The first version had 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 shows the everyday production range of up to 630 A next to it and explains where the design limit lies. The section on shipping to the rest of Russia was written after reading the primary sources: the region is a special economic zone, goods leaving it go through customs, and for the buyer it is an ordinary domestic delivery. A shipment figure mentioned in conversation was taken off the site until it is confirmed. A separate pass rewrote 31 sentences built on contrast as plain statements and cut sentences repeated across pages from 16 to 4.

Two builds, and three locks on indexing

The site is built with 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. The diagram uses realistic data that belongs to nobody, since a scan of a real diagram would show the customer's project in the title block. Before the company's domain existed, the site was built in a subfolder of a domain that already had 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 that is missing the subfolder prefix. The logo linked to "/", the home page of the host domain, and a simple "the target exists" check saw nothing wrong.

Until the text was approved, the site was hidden from search engines in three ways at once: noindex in the markup, the X-Robots-Tag header and robots.txt. All three had to come off together, since the header alone would have kept the site out of the index. The switch lives in the deploy script, so the next deploy can't close the site again. The DNS cache time went down from a day to an hour, so if the site ever has to move to another server, visitors reach the new address within the hour.

A form that keeps the diagram

In this trade a request almost always comes with a single-line diagram. The request is saved 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 shown on the page with the same numbers the service enforces. Checks that ran only in the browser moved to the server: consent to data processing, file type, a hidden trap field and the time taken to fill in the form. A form sent in under two seconds is treated as filled in by a program and quietly dropped.

A 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 fails the check reaches the manager in a separate "attachments not accepted" block. The Telegram inbox was losing requests. Text went to the group as HTML without escaping, so a less-than sign in an everyday phrase like "input < 630 A" broke the whole message, while the sender still got an automatic "received" reply. Escaping is now in place, and the log records Telegram's full response.

Why incoming calls dropped at once

The Rostelecom cloud PBX was connected, and the routing rule was fine: active, around the clock, to the group. An incoming call reached the line and dropped immediately; the call log showed a single step, "call to the inbound line". The cause was in the card of the only user. The mobile number in the call scenario was switched off with a toggle, and no SIP device (an internet desk phone or a softphone app) was attached, so the PBX had nowhere to ring. In the scenario diagram a disabled branch differs only by a grey frame, so each number's card has to be checked separately. Along the way I fixed the domain's time zone (the call log had been an hour off), set a default number for outgoing calls, and switched on voicemail notifications, which had been set to "do not send".

Then came the structure: three users within the plan (management, accounting, technical department), each with a mobile. The line count went from 2/2 to 6/6. The synthesised voice menu lasts 13.6 s. Pressing a digit sends the call to a department, and if nothing is pressed it goes to management. Two stock recordings, switched on by default in the domain, were turned off: callers had been hearing someone else's text after the new 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 waiting made 22 s before the first ring, 8 of them in silence. The waiting time was cut to 6 s, so the first ring now comes at 19.6 s. The only thing left to shorten is the menu text itself.

The office number printed on the certificates wasn't in the PBX domain at all: test calls to it never appeared in the log. The request to the operator was rewritten on the basis of those facts, with three requirements and a list of what was already set up in the PBX. The number was moved on 1 September, on a request filed on 28 August. It arrived with its own rule that sent every call straight to the group, past the voice menu. I switched the rule by hand, and both numbers now lead to the same menu. While the move was pending, 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 temporary number was removed from all seven pages of the live site.

The GigaChat assistant says plainly what it doesn't know

It answers questions about switchboards and low-voltage assemblies, and when it has no answer it takes the visitor's contact details. The engine is FastAPI with streamed replies; the chat window uses no libraries. The knowledge base is about 3,200 tokens and fits entirely into the system prompt. So there is no vector search, on purpose: at this size, search would only add a point of failure and a chance of missing the right piece. Half of the rules list what the bot doesn't know: prices, lead times, warranty, maximum current, component brands, past projects, certificate numbers. For each of these it says what exactly a manager will clarify and offers to take a phone number. It was tested with a question on every item and made nothing up. Asked about a licence, it explained that building LV switchgear doesn't require a licence and named the technical regulations TR CU 004/2011 and 020/2011.

A request is saved to disk first, then sent to the messenger, and it carries the whole conversation. The Russian state root certificate, which connections to Sber need, is passed only to that one connection: a state root installed system-wide could vouch for any domain on this machine. The rate limiter used to read the visitor's address from a header the client supplies itself: 25 requests with made-up addresses went through without a single refusal, and each was a paid model call on the owner's account. The limit moved into nginx, keyed on the real connection address, at 20 requests a minute, and the same 25 requests now run into it. Services no longer run as root; the chat had been running with the full set of 41 Linux kernel privileges (capabilities). Secrets are passed through EnvironmentFile, and request and transcript files are closed to other users (mode 640 in 750 folders).

Listing, markup and search indexing

The Yandex Business listing was created with the same data as the site. Weekday hours of 9:00–18:00, given by the owner, are the same in the listing and in the markup. The listing and the site appear side by side in search results, and different answers to the same question look careless. The markup had been of type Organization, a legal entity with no place on the map. I added a LocalBusiness type with opening hours, a service area of "Russia", a link to the listing and coordinates. The link connects the site and the listing in both directions and helps a search engine see that both belong to one company. The coordinates taken from the listing page in Maps turned out to be the centre of the visible map area, two kilometres off. I checked them by geocoding the building's address and then reverse-geocoding the point.

Indexing was opened on the owner's decision, together with a Yandex Metrica counter with session replay and site verification in Yandex Webmaster and Google Search Console. By 25 August Google had six pages in its index; Yandex first visited on 27 August. The deploy tells search engines about changes by itself through IndexNow, a protocol made for exactly that. The addresses 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 addresses sat in the nested file of a two-level sitemap, and the nginx log showed the crawler fetching the index file nine times without going on to the nested one. I added a second Sitemap line to robots.txt pointing straight at the file with the addresses, and sent seven addresses for recrawl.

  • A favicon in four files: the Webmaster crawler asked for /favicon.ico and got a 404, even though the SVG version was there
  • A 1200×630 link preview for messengers made from a workshop photo, 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 being cut off with an ellipsis

The server: headers, permissions, backups

In nginx, a nested block that sets any header of its own stops inheriting the headers from above (add_header). As a result, CSS, fonts and images were served with no security headers at all. The set moved into a separate file that every block includes, and static files now carry four headers, nosniff among them. The server version is hidden, and TLS 1.0 and 1.1 are off. The content security policy sat in a file in a format nginx doesn't read, so it was never sent. It's now set in the config, and the stray file, which the server had been handing out with a 200, is closed. The services had no memory limit: on a 1.9 GB machine a few uploads at once could take all the memory and bring down nginx, which serves both sites. Limits of 384 MB for the form and the chat and 192 MB for the inbox are now set, and nginx restarts 5 seconds after a crash. The whole server configuration is copied into the repository; the old copy had fallen 25 lines behind the live server.

Backups didn't cover the project at all. They now include requests, attachments, the chat log and a snapshot of the knowledge base. Secrets are left out on purpose, since they can be reissued in a minute. The backup check used to look only at file size, and a transfer cut off halfway produces a file of the right shape and a convincing size that won't unpack. So I added unpacking and a check that the requests are inside. A test restore produced 22 files with all three request files in place. A few days later the check began raising false alarms on good archives. It stopped reading the archive as soon as it found the first request, the archiver treated that as an error, and the script marked the whole check as failed (grep -q, SIGPIPE and pipefail): the sooner the requests were found, the more surely the alarm went off. A second machine pulls the backups with a key that allows only that one command. The same machine checks both sites every five minutes. Each of its logins is recorded in the server's login log, and if there has been no login for more than 26 hours, the daily summary says so in Telegram. That line appears only when something is wrong: the two machines keep an eye on each other with no outside services.

An evening summary based on real counts

Every evening a summary 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". It was true until 19 August and kept appearing every evening after that. Now visitors are counted from the nginx log on every run, so lines like that stay current. The first count filtered out robots by name and reported 30 people in a day when there were two real ones: scanners have long presented themselves as ordinary browsers. Filtering by behaviour works: only a browser that actually draws the page requests the font. The new count gives 29 people a week against 26 in Yandex Metrica. The gap of three people is the method's margin of error; before the fix the gap was 178. The summary says "approximately" and points to Metrica for the exact figure.

Mobile layout and page weight

At 375 px the first screen of three pages spilled over the right edge: by default a grid item can't be narrower than its content, and the column with the single-line diagram stretched to 586 px. After the fix the column is 335 px, and the diagram scrolls inside its own frame. Production photos got WebP versions and a JPEG fallback at 1600 and 800 px. The seven photos on the production page went from 3,200 KB to 1,168 KB on a computer and to 415 KB on a phone, at full resolution. The main product photo went from 627 to 147 KB on a phone. Ten Lighthouse runs after lowering the priority of a photo further down the page: LCP 2.40 → 2.10 s, page weight 367 → 324 KB, no layout shift. Typography is part of the build: a script places 830 non-breaking spaces. Contrast by the WCAG formula is 17.8:1 for text and 8.0:1 for the accent colour. The main phone number appeared 5–8 times per page. On the home page it went from 8 to 6 occurrences, and the full list of numbers is shown once.

03 · Technology

A static site, one server and the providers' own services

Astro 5 · Tailwind 4

A static build in two versions, for the company's domain and for a demo subfolder, with link checks and typography built in

nginx · Let's Encrypt · systemd

Security headers on all static files, a content security policy, request limits, services not running as root, restart after a crash

Python · FastAPI

Request intake with attachments and the chat engine: disk first, then Telegram; consent and file-type checks on the server

GigaChat

The chat assistant, with a knowledge base of about 3,200 tokens in the system prompt and a list of what it doesn't know

Rostelecom cloud PBX

A voice menu for three departments, calls forwarded to mobiles, the office number moved into the PBX

Yandex Business · Metrica · Webmaster · IndexNow

A listing with the same hours and phone as the site, a counter with session replay, LocalBusiness markup, search engines notified on each deploy

Astro 5Tailwind 4nginxLet's EncryptsystemdPythonFastAPIGigaChatRostelecom cloud PBXYandex BusinessYandex MetricaIndexNowSchema.orgTelegram Bot APILighthouse
04 · Results

Measurements before and after

Incoming call to the office number
dropped at once 19.6 s

until the first ring: 13.6 s of voice menu and 6 s of waiting; a caller who presses a digit gets through sooner

Production photos on a phone
3,200 KB 415 KB

seven photos on the production page; 1,168 KB on a computer, 1600 px resolution kept

Main content load on the home page (LCP)
2.40 s 2.10 s

ten Lighthouse runs; page weight 367 → 324 KB, no layout shift

Visitor count in the evening summary
3 people: the gap between the weekly count and Yandex Metrica; before the fix it was 178

Filtering robots by name let through scanners posing as ordinary browsers: 30 people in a day when there were two real ones. Filtering by behaviour (the font request) gave 29 people a week against 26 in Metrica. The summary says "approximately": the log gives the order of magnitude, and the exact figure is in Metrica.

The site works on its own domain, with eight pages. The form accepts 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 had six pages in its index, and Yandex first visited on 27 August. After the sitemap fix on 6 September, seven addresses were sent for recrawl. A buyer who types in the company name now finds the site, the listing and a working phone line.

The chat assistant works on the site. It was tested with questions it has no answer to: it made nothing up and offered to take a phone number so that a manager could follow up.

One limitation remains: the site runs on one server, and there is no standby yet. On 18 August an outage at the data centre's telecom operator took it down, together with a neighbouring server that my changes never touched. That's why the DNS cache time is down to an hour, and the backups go to a second machine and are checked by unpacking. If the site has to move to another server, visitors will reach the new address within about an hour.

Between 18 August and 6 September the work log has 92 entries, and each ends with a check on the live site: page response codes, headers, the layout at 375 px, a full test request with an attachment, and real calls traced in the PBX logs.

05 · Where it fits

Where the same approach helps

The underlying task is common: an established business goes online for the first time. The website, the listing and the phone are set up together and rest on one file of facts.

  • → A workshop or plant with no website: buyers check a supplier against official registries in a minute, and a site, a listing and full company details help it pass that check
  • → An office line nobody answers: a cloud PBX with a menu by department and calls forwarded to mobiles, checked against the logs of real calls
  • → Requests from the site get lost: each request is saved to disk before it goes to the messenger, attachments travel as separate messages, and the reason for a rejection is shown to both the visitor and the manager
  • → The listing and the site disagree: hours, phone and address come from one source, and the LocalBusiness markup links to the listing
  • → A chat assistant for a narrow trade: a fact base that fits in the system prompt, with half the rules listing what the bot doesn't know
What I reuse on later projects
  • One file of facts: a number goes onto the site, the listing or the chat's knowledge base only from a document or the owner's words
  • Request intake that saves to disk first and sends to the messenger second, with attachments as separate messages and checks on the server
  • Backup checks by unpacking and by looking for the requests inside; two machines watching each other through the login log
  • A cloud PBX checklist: each number's card, the time zone, stock recordings, time to the first ring measured from the logs
  • An evening summary that measures the current state on every run and mentions backups only when something is wrong
Similar challenge?

If a plant has no website and nobody answers the office line, both can be fixed in one project

I start with a file of facts: what the documents and the owner's own words confirm. The site, the listing and the phone system are built on it, and every change is checked on the live site and with real calls.

Ready to start?

Start with the 5,000 ₽ audit: a written report and a payback estimate

I'll tell you what to automate first, when it will pay for itself and whether your task needs AI at all. Sometimes it doesn't.

Or just ask a question. During business hours I reply within 2 hours