Tontić Lux
Holiday apartmentsKopaonik, Serbia
One form, three ways to reach the owner, and every path lands in the same sheet.
Status
Services
- Website · Booking & orders · Lead pipeline & CRM · Automations
Year
2026
Results
Not measured yet.

The problem
Three apartments in two buildings, and prices that change by season, so every quote is written by hand. A quote depends on the unit, the dates, the group size and the children's ages. Our project notes put the risk plainly: a guest who doesn't get a ballpark price goes to a competitor.
What we built
- An 11-page site: apartments, amenities, gallery, location, a 15-question FAQ and an inquiry page.
- A 4-step inquiry: unit and dates, guests and children's ages, contact, send.
- A range calendar that enforces the two-night minimum and works with arrow keys.
- Three exits from one form: send to the owner, WhatsApp or Viber. Each one is logged to the same sheet. A prefilled Booking.com exit is built and switched off.
- A Google Sheet pipeline with five statuses, colour-coded rows and only three columns the owner fills by hand.
- An owner email with a one-tap call button. Hitting reply answers the guest directly.
- A guest confirmation, a reminder 3 days before arrival and a feedback request 2 days after checkout.
- One plain config file for contacts, stay rules and the response-time promise.
How it works
The flow end to end, as the system is built to run. Sheet and status names are the client's own.
10 steps · 1 sheet
- Guest picks a unit
- Dates, two-night minimum
- Guests and children's ages
- Send, WhatsApp or Viber
- Sheet row · Novo
- Owner email, reply-to guest
- Guest confirmation
- Owner quotes · Potvrđeno
- Reminder 3 days before
- Feedback 2 days after
Under the hood
The details a developer checks first. Most of them exist so an inquiry is never lost or sent twice.
- The server re-checks the essentials: name, email, phone, both dates, checkout after check-in, two nights minimum.
- Rate limit: three inquiries per email in ten minutes.
- WhatsApp and Viber clicks log the inquiry in the background. A signature check writes one row, not three.
- Each email is sent on its own, so a failed email never loses the row.
- Backend down? The guest falls back to WhatsApp or a prefilled email. No dead ends.
- The form survives a refresh. Nothing the guest typed is lost.
- Reminders run once a day and mark each row, so nothing is sent twice.
- Keyboard calendar, errors announced to screen readers, focus on the first invalid field.
Stack
What it runs on.
- HTML · CSS · Vanilla JS · Google Apps Script · Google Sheets · MailApp · Python build · Playwright QA
Build QA
From our own test runs before launch. These are build numbers, not business results.
- Crawl of all 11 pages: 503 internal references, 0 dead.
- 11 pages with 0 console errors, exactly one h1 each and no broken images.
- 14 JSON-LD blocks valid. The sitemap matches the real pages.
- A triple click sends 1 request. Server error, network failure and a non-JSON reply all end in a WhatsApp or email exit.
- No horizontal scroll at 360 and 375 px.
- Fixed during QA: 2 blockers and a duplicate-row bug.
What we didn't build, on purpose
Scope is a decision. Every line here was decided, not forgotten.
- No pricing engine. Prices change by season, so the owner sends them with the reply.
- No availability calendar. The sheet tracks inquiries, not occupancy.
- No online payment. A stay is reserved when the owner confirms it, not before.
- No WordPress, no Webflow, no monthly subscription. Static files and the owner's own Google account.
The build
Captured from our local pre-launch build. Phone numbers, email addresses, prices and photos of people from the client's site are blurred wherever they appear.



