Work / Tool Mothballed
Viewing Tracker: from a pile of enquiry emails to one dashboard
Viewing Tracker reads rental enquiry emails from a Gmail label, asks Claude to extract who is coming to an open house and when, and turns the results into a single dashboard with status, arrival times and a list of enquiries still to answer. It served one open house and is now paused.
Demonstration data. Invented data.
Four confirmed, one likely, one no reply, one declined, and two enquiries still waiting for an answer. That is the state of an open house on one screen, with no thread to open. Viewing Tracker reads the enquiry emails for a rental listing, asks Claude to work out who is coming and when, and keeps the answer in a dashboard I can open in a browser.

At a glance
- Status: Mothballed. It served one open house in May 2026.
- Platform: a command-line Python tool that writes a standalone HTML page. Nothing is hosted.
- Stack: Python, the Gmail API, the Anthropic API, SQLite, and plain HTML and CSS for the dashboard.
- Built with: Claude Code and Codex.
- AI inside the product: yes. Claude reads each email thread and returns a structured record. Details below.
The dashboard
Text version of the video
- The dashboard at the top: counts for confirmed, likely, no reply, declined and enquiries that need a reply.
- Scrolling down: one card per enquiry with the status, a short summary of what they asked, the type of reply they received and any arrival time.
- The unanswered enquiries at the bottom.
- The protected copy of the same page shows only a password box. A wrong password shows "Incorrect password, try again". The right password decrypts the dashboard in the browser.
A rental listing produces a stream of short emails. Each one says who someone is and when they want to move, and some are replies to my invitation to an open house, with an arrival time buried in the second line. Before the evening I needed to know how many people were coming and who still needed an answer, and this tool answers both without opening a single thread.
From threads to cards
It finds the threads I have labelled in Gmail and sends each one to Claude with a fixed prompt. Claude returns a small JSON record: the person’s name, email and phone if given, a two or three sentence summary, whether they confirmed, an arrival time, and one of five statuses. A second kind of email, the notification from the listing site, is read separately into a list of enquiries I have not replied to yet. Everything is stored in a local SQLite file, and a script turns the file into the dashboard.


What Claude does with a thread
Here is what happens to three invented threads. In one, a prospective tenant writes that he and his partner both work from home. The landlord’s reply gives the open house time and warns that the second bedroom is small. He answers that it sounds worth a look and he will confirm. This is the record that came back:
{
"name": "Ben Placeholder",
"confirmed": false,
"arrival_time": null,
"status": "likely_attending",
"landlord_reply_type": "custom",
"landlord_reply_notes": "The landlord flagged that the second bedroom is on the small side and may not suit two people working from home."
}
Claude did not count him as confirmed, because he had not said yes or given a time. It also noticed that the landlord’s reply was tailored rather than the standard invitation, and kept the warning as a note. The other two threads came back as confirmed with an arrival time of 7:40pm, and declined because of a night shift.

Keeping a person in charge
A wrong status on an open-house list is a wasted evening, so three decisions in the code keep me in control of the result.
Build decision. A strict meaning for “confirmed”. The prompt says a record is confirmed only when the person explicitly said yes and gave an arrival time. A yes with no time is a separate status, and a positive but vague reply is “likely attending”. The dashboard shows how many of the confirmed have a time and counts likely attendees apart, so a hopeful reply is never mistaken for a yes.
The second decision is that a correction beats the model. When I fix a record by hand, it is marked as manually overridden and later refreshes from Gmail leave it alone. The third is that anything which sends email is gated. Reminders go only to people who have not replied or have not confirmed, a dry-run option lists them first, and the tool asks for a yes before it sends.
Under the hood
I built Viewing Tracker with Claude Code and Codex as a single-file Python command-line tool. It reads Gmail with a read and send permission, builds a text version of each thread, calls Claude, and writes the JSON into two SQLite tables, one for replied enquiries and one for unanswered ones. The dashboard is generated straight from those tables into one HTML file with its own CSS, so it needs no server and makes no network calls.
| Layer | What I used |
|---|---|
| Interface | One generated HTML file with its own CSS; no server, no network calls |
| Language and runtime | Python, run from the command line |
| Integrations | Gmail API (read and send), Anthropic API |
| Data | Local SQLite: one table for replied enquiries, one for unanswered |
| Runtime AI | Claude, filling one fixed record per thread |
| Output protection | Optional AES-256-GCM encrypted copy, key derived from a password |
| Hosting | None: it is a command-line tool |
The protected copy encrypts the dashboard with AES-256-GCM, using a key derived from a password, and wraps it in a page whose script decrypts it in the browser.

What the design leaves to code
The model’s job is narrow: it fills one fixed record per thread. Counting, ordering and everything on the dashboard is ordinary code working from rows in a table, so any number on the screen can be traced back to the records behind it. Viewing Tracker shows how a language model can turn a messy inbox into a trustworthy open-house list: Claude reads the threads, strict rules and manual overrides keep the statuses honest, and a person stays in charge of every email that gets sent.