I built an automation that loads a specialist’s booking site every ten minutes, checks whether a slot has opened, and if one has, emails me immediately with a screenshot while trying to book it itself. Four npm dependencies, running on a tiny cloud server rather than my laptop. What I learned is that the “smart” half was not the smart half: the alert is the product, and the automatic booking is a bonus that has yet to prove itself.
The setup is mundane. There were no appointments with the doctor I wanted. But slots do open, constantly, because people cancel. The problem is not availability — it is that no sane person loads a website 144 times a day to find out.
What it actually does
Playwright drives Chromium with a persistent profile, an he-IL locale and Israeli time, replaying exactly the steps I would take by hand:
- Open the doctor’s mini-site on rofim.org.il, a directory Israeli specialists list on, and dismiss the banner.
- Pick the insurer: Clalit Mushlam, the supplementary tier of Clalit, Israel’s largest HMO.
- Continue to booking, landing inside an iframe served from a different domain, shidurit.com, where the actual appointment screen runs.
- Type a teudat zehut (Israeli national ID number) and submit. No SMS, no OTP — the ID number is all that field asks for. There is a further identification layer on the site’s side, which comes up later.
- Read the availability screen.
Classifying that screen is the heart of it, and it is three lines:
- The screen loaded and contains “no available appointments” → no slot. Counted silently, no email.
- The screen loaded and does not contain that line → slot. Email immediately.
- The screen did not load at all → error, not “no slot”.
That third line is the difference between an automation you can rely on and one that lies to you quietly. A changed site, an expired session, an element that moved — all look exactly like “no appointments” unless you confirm you arrived somewhere real. So before concluding anything the code looks for positive anchors in the screen text — Hebrew phrases like “תור אצל” (appointment with), “התורים העתידיים” (upcoming appointments) and “שינוי מטופל” (change patient) that only render on a loaded patient screen. No anchor, no conclusion.
Every run writes its own directory with a screenshot and a log. Excessive hygiene for an evening’s script, right up until the first time you ask why you never got an email.
Alongside it sits a second script that is purely a development tool: it walks the same path with a visible browser, dumps every clickable element at each step — tag, id, class, text — and saves the final screen’s HTML. That is how the selectors were found, and how they get found again the day the site moves. In an automation riding on someone else’s UI, the tool that rediscovers the UI is worth as much as the code that drives it.
The pattern: poll a bad website until something changes
The project itself is not interesting. What is interesting is that it instantiates a pattern behind dozens of everyday problems:
- A scarce resource frees up at random times.
- The only way to know is to go look.
- Whoever looks first gets it.
A vehicle inspection slot, a driving-test cancellation, stock returning to a shop, a consulate appointment. Same shape, different institution. Terrible for a human, since it demands persistence at a cadence nobody has. Ideal for a small local automation.
Notice what is absent. No model, no complex judgment, no API integration. A browser, a loop, and a stop condition. The AI is not in the runtime at all; it is in the writing. With an agent that writes and runs code, the distance between “this annoys me” and “this runs in the cloud” is one evening. Nobody would scope it as a project up front, which is precisely why people should be able to build these for themselves.
What deserves thought is not the loop. It is the edges.
The alert is the product. The booking is a bonus.
Here is the confession: when I built this there were no slots at all, which means I never saw the screen where you pick a time and confirm. So the code does not pretend to know it. It does two separate things:
- It always sends an email the moment it detects a slot, with a screenshot and the text of the screen. That is the channel I trust, because it depends on no selector.
- It attempts to book: it scans every clickable element in the frame, keeps those whose text looks like a time in
HH:MMform, filters them against an optional hours window (mine is left wide open), clicks the first one in sort order, then hunts for a confirm control from a list of Hebrew words — approve, book, I confirm — and checks the resulting page text to verify the appointment landed. Even that sort is a guess: it compares strings, not times, so it holds only if the screen renders “09:30” rather than “9:30” — and I have not seen the real screen.
And if it recognizes nothing? It saves the screen’s full HTML plus the screenshots and leaves the booking to me. A failed auto-booking is not a failed system — it demotes it one rung, from “it is done” to “go do it now”, and the first run that meets a real slot captures exactly the evidence needed to close the gap.
That is still the state as I write this. No slot has been caught since it went live, so the booking-screen selectors remain unfinished and the booking is still mine to do. A MODE=watch switch disables the attempt entirely and leaves only alerts.
Which brings in a small detail that is really the main point: if the alert is the product, the first time it fires must not be the first time it is tested. So an environment variable forces the code to treat the screen as though a slot were on it, and sends the real email with a [TEST] prefix. That is how I knew the channel worked without waiting for an event two months away.
What matters is what it does not do
Most of the work in an automation like this lives in the limits. My rule: it does exactly what I would do by hand, at a reasonable rate. It does not fight the institution it queries.
- Rate. One check every ten minutes. Fast enough to catch a cancellation, nowhere near anything resembling an attack.
- One request in flight. If the previous check is still running, the next tick skips. Nothing piles up.
- Silence when there is no news. “No appointments” is only counted. Mail arrives when something happened, plus one daily summary at 21:00.
- One alert per failure. After six consecutive errors — about an hour at this cadence — a single email says the automation is probably broken. One, not six, latched until things recover.
- A latch after success. Once an appointment is booked it stops entirely. You delete the state file to re-arm it.
And one rule that lives in the behavior rather than the code: if you catch an earlier slot, cancel the later one. The whole thing works because people cancel. An automation that grabs and never releases breaks the mechanism it exploits.
Where it runs
It started on my Mac as a launchd agent, but laptops close and travel and a cancellation does not wait. So it moved to a tiny ARM server on AWS in the Tel Aviv region, as a systemd service that restarts itself. I chose the Israeli region because an in-country IP is the one I confirmed passes the site’s identification step; I never tested what happens from outside Israel. Around nine dollars a month. Each check launches a fresh browser and closes it afterwards, so the process sits nearly empty between runs and a tiny machine is enough.
The takeaway
A lot of what gets called “automation” inside organizations is exactly this: somebody opens a screen every so often to see whether something changed, then takes a small action. A shipment status, a tender that opened, a field in the CRM. It does not feel like a technology project because it is not one.
What takes judgment is a separation I ended up with without planning it: separate knowing from acting. The knowing half has to be robust, explicit, and confident only when it holds evidence. The acting half may be fragile, as long as it fails loudly and downgrades itself into a smaller problem instead of disappearing.
This is what the process looks like when I run it with an organization — connect the tools first, then teach people to build these small things for themselves.