Apartment Agent prototype for Bangkok apartment search automation.
Desktop apartment-search agent for Bangkok rentals, built to monitor Thai listing sites, score results against custom accessibility-focused criteria, and generate agent outreach emails that can be copied or opened directly in Gmail.
The current implementation is centered on PropertyHub, because its pages expose a structured Next.js payload that is much more reliable than brittle HTML scraping alone. Results are stored locally in SQLite, surfaced through a Tkinter desktop app, and enriched with agent contact details, match scoring, contact-tracking state, and draft emails.
- Pulls apartment listings from configured Thai rental search pages
- Fetches richer detail pages for likely matches
- Extracts listing facts such as rent, size, beds/baths, project, transit references, amenities, and agent contact details
- Scores listings against user-defined criteria such as budget, size, bedroom count, target neighborhoods, and walkability cues
- Deduplicates repeated listings in SQLite
- Generates first-contact outreach emails for real-estate agents
- Opens Gmail with the draft prefilled, copies the draft to the clipboard, or sends the email directly from the app through SMTP
- Includes a desktop
Settingspage for managing the direct-send SMTP account in a local.envfile - Lets you save a missing agent email manually or pull candidate emails from the built-in research flow
- Lets you mark listings as
contactedornot contacted - Tracks whether a listing has been
viewedoremailed, separately from general contact state - Lets you hide listings as
not interestedand keep them hidden across future runs - Shows listing date, viewed/emailed/contact state, and hidden-state directly in the desktop UI
- Sorts results by best match, newest, oldest, and price
- Lets you click table headers to sort directly in the results grid
- Writes JSON and Markdown reports for each collection run
- Optionally captures screenshots with Playwright for manual review
The goal was not just to scrape listing pages. The real workflow was:
- Search repeatedly for apartments in a very specific Bangkok corridor
- Keep only listings that fit a real-life living situation
- Track which agents have already been contacted
- Open a ready-to-send outreach message quickly
Because of that, the app was built as a local workflow tool instead of a pure crawler.
Key design decisions:
Pythonfor fast iteration and a low-friction local toolchainTkinterfor the desktop app because it ships with Python and can be built quickly without a frontend stackSQLitefor persistent local tracking of listings, drafts, and contacted status- Adapter-based site collection so each portal can have its own extraction logic
- Structured payload extraction from
__NEXT_DATA__on PropertyHub instead of relying only on CSS selectors - Deterministic email generation so the outreach flow works without requiring an LLM or API key
- Optional Playwright support as a fallback for browser screenshots and future browser-control workflows
This project was assembled in layers:
The first step was defining stable Python dataclasses for:
- search criteria
- search sources
- listings
- email drafts
That created one consistent schema for scraped data, scoring, UI display, email drafting, and persistence.
The next step was implementing a rules-based matcher that scores listings for:
- budget fit
- bedroom count
- minimum size
- target neighborhoods and transit anchors
- walkability / park-related cues
- furnishing preference
- conflicting or suspicious data
This keeps the decision process inspectable and easy to tune.
SQLite was added to:
- keep listing history across runs
- deduplicate repeated or reposted units
- store generated drafts
- preserve
contactedandnot interestedstate even after a fresh search
PropertyHub was implemented first because it exposes rich embedded JSON data. That made it possible to extract:
- listing IDs
- prices
- room details
- project names
- contact information
- dates
- amenities and facilities
without depending entirely on fragile visual scraping.
A command-line runner was added to support:
- seed/demo runs
- live runs
- daily scheduled runs
- screenshot capture
- launching the desktop app
Each run also produces a JSON and Markdown report under outputs/.
Finally, a local Tkinter app was added so the project is actually usable day-to-day.
The app lets you:
- run the search
- filter and search results
- review agent contact info
- see listing dates
- mark listings as contacted
- regenerate outreach drafts
- copy drafts
- open a prefilled Gmail compose window
Implemented:
PropertyHubzone pagesPropertyHubproject pagesPropertyHublisting detail pagesHipflatbrowser-backed detail discovery- SQLite persistence
- results viewer desktop app
- Gmail draft opening
- direct SMTP sending from the desktop app
- contacted tracking
- persistent not-interested hiding
- listing date display
- date-based result sorting
- optional Playwright screenshot capture
Not implemented yet:
- DDproperty adapter
- Thailand-Property adapter
- RentHub adapter
- PropertyScout adapter
- OCR / vision extraction from screenshots
- multi-user or hosted deployment
apartment_agent/models.pyCore dataclasses for criteria, sources, listings, and draftsapartment_agent/adapters/propertyhub.pyPropertyHub collection logicapartment_agent/matching.pyRules-based scoring and conflict detectionapartment_agent/storage.pySQLite persistence, draft storage, contact state, hidden-state tracking, and list/query helpersapartment_agent/email_drafts.pyOutreach email generationapartment_agent/gui.pyTkinter desktop appapartment_agent/cli.pyCLI entrypoints for runs, app launch, and screenshot captureconfig/criteria.jsonUser criteria and outreach contextconfig/sources.jsonSource pages to monitorconfig/seed_listings.jsonSeed/test data
python -m apartment_agent run-seed --criteria config/criteria.json --seed config/seed_listings.jsonpython -m apartment_agent run --criteria config/criteria.json --sources config/sources.jsonpython -m apartment_agent apppip install -r requirements-optional.txt
playwright install chromiumpython -m apartment_agent capture --url "https://propertyhub.in.th/en/condo-for-rent/mrt-chatuchak-park" --output screenshots/chatuchak-park.pngInside the app you can:
- click
Run Searchto refresh listings - use
Filterto switch betweenalert,watch, andreject - use
Contactedto show only contacted or uncontacted listings - use
Interestto hide or review listings markednot interested - use
Sortto prioritize newest or oldest listings by date - search by title, project, URL, site, or listing ID
- select a result to inspect contact info, match reasons, flags, and summary
- click
Mark Contactedonce you have reached out - click
Hide Listingonce you know a listing is not worth reviewing again - click
Restore Listingto bring a hidden listing back into the active set - click
Regenerate Draftto rebuild the latest email from the current template - click
Send Emailto send directly from the app when SMTP is configured - click
Open Gmail Draftto open a browser tab with the draft prefilled - clicking the Gmail action marks the listing as
emailed - click
Copy Emailto paste the message elsewhere - open the
Settingstab to manage the SMTP account used for direct sending - use
Test SMTPin theSettingstab to verify login before sending live email - use
Find / Set Emailin theEmail Drafttab when the listing is missing an agent email
The desktop app can send an email directly without opening Gmail.
You can either edit .env directly or use the Settings tab in the app. The repo includes .env.example, and the app uses a local .env file that is ignored by git.
Default Gmail profile:
$env:APARTMENT_AGENT_SMTP_HOST="smtp.gmail.com"
$env:APARTMENT_AGENT_SMTP_PORT="587"
$env:APARTMENT_AGENT_SMTP_USERNAME="pzgambo@gmail.com"
$env:APARTMENT_AGENT_SMTP_PASSWORD="your-app-password"
$env:APARTMENT_AGENT_SMTP_FROM="pzgambo@gmail.com"
$env:APARTMENT_AGENT_SMTP_FROM_NAME="Patrick"
$env:APARTMENT_AGENT_SMTP_REPLY_TO="pzgambo@gmail.com"
$env:APARTMENT_AGENT_SMTP_USE_TLS="1"For Gmail, use an app password, not your normal Google account password.
Once configured, selecting a listing and clicking Send Email will:
- send the current draft to the listing agent's stored email address
- save that sent draft to SQLite
- mark the listing as
contacted
Hipflat is now included as a browser-backed source. In practice, Hipflat often sits behind a Cloudflare challenge, so the adapter may need a verified Playwright profile instead of a fully anonymous browser session.
If Hipflat is blocked, set a persistent profile and run headful once:
$env:APARTMENT_AGENT_BROWSER_PROFILE_DIR="C:\\temp\\apartment-agent-browser"
$env:APARTMENT_AGENT_BROWSER_HEADFUL="1"
python -m apartment_agent run --criteria config/criteria.json --sources config/sources.jsonThe same environment variables also apply when running the desktop app.
The simplest Windows setup is Task Scheduler calling:
python -m apartment_agent run --criteria config/criteria.json --sources config/sources.jsonThere is also a built-in long-running scheduler:
python -m apartment_agent run-daily --criteria config/criteria.json --sources config/sources.json --time 09:00 --timezone Asia/BangkokListings are scored based on:
- maximum monthly rent
- minimum bedrooms
- minimum size
- preferred locations and transit anchors
- park and walkability cues
- furnishing preference
- page/data conflicts
- missing or unreliable detail pages
Listings are then labeled:
alertfor strong candidateswatchfor possible candidatesrejectfor poor fits
Draft emails are generated from:
- the selected listing details
- stored outreach context in
config/criteria.json - accessibility/mobility requirements
- the requested Bangkok viewing window
- one or two listing-specific questions
If an agent email is available, the Gmail action prefills the recipient as well.
Each listing now stores:
contactedcontacted_atlisting_date
This makes it possible to filter for listings you still need to contact and avoid losing state after re-running the search.
- The project currently relies most heavily on PropertyHub.
- Older stored listings may not have a listing date until a fresh search refreshes them.
- Some sites redirect or expose inconsistent fields, so future adapters will still need site-specific handling.
- This is a local desktop tool, not a hosted SaaS product.
- Email sending is still review-first. The app can send directly, but only when you explicitly click
Send Email.
MIT. See LICENSE.
python -m unittest discover -s tests -v
python -m compileall apartment_agent testspython scripts/render_demo_gif.pyThis repo is a pragmatic local apartment-hunting workflow tool. It combines site collection, ranking, persistence, contact management, and email drafting into one desktop application so apartment outreach is fast and organized instead of spread across browser tabs, chat logs, and copied notes.

