A production pricing automation system managing 6 vacation rental properties across multiple distribution channels. Built as a solo developer to replace manual spreadsheet pricing with an automated pipeline that gathers market data, runs AI-powered analysis, and pushes optimized rates to every booking platform in real time.
The system handles the full lifecycle: ingesting bookings from channel managers, detecting pricing opportunities through pace analysis, generating AI-backed rate recommendations, routing them for owner approval, and syncing approved rates across Airbnb, Booking.com, VRBO, and a direct booking site.
This is a private production system. Code samples in this repository are sanitized -- engineering patterns are fully visible, but proprietary business logic, thresholds, and prompts have been redacted.
flowchart TB
subgraph Channels["Distribution Channels"]
AB[Airbnb]
BC[Booking.com]
VR[VRBO]
DB[Direct Booking Site]
end
subgraph CM["Channel Manager (Channel Manager)"]
WH[Webhook Events]
API[Rate Push API]
end
subgraph Core["Supabase Backend"]
direction TB
EF["Edge Functions<br/>(Deno/TypeScript)"]
CR["Cron Scheduler<br/>w/ Dependency Ordering"]
DB2[(PostgreSQL)]
DLQ["Dead Letter Queue"]
AL["Advisory Locks"]
end
subgraph AI["AI Advisor"]
GPT[GPT-4 Analysis]
PA[Pace Analysis]
VP[Velocity Projections]
end
subgraph Auto["Browser Automation"]
PW[Playwright Runtime]
VS[VRBO Price Sync]
end
subgraph Notify["Notifications"]
TW[Twilio SMS]
EM[Email Alerts]
end
subgraph Frontend["React Dashboard"]
DP[Pricing Proposals]
AD[Approve / Deny]
AN[Analytics Views]
end
subgraph POS["Point of Sale"]
SQ[Square POS]
CA[Cafe Analytics]
end
Channels <-->|bookings| CM
CM -->|webhooks| EF
EF -->|rate updates| API
EF -->|VRBO rates| PW
PW --> VR
API --> AB & BC
EF <--> DB2
CR --> EF
EF --> DLQ
DLQ -->|retry| API
EF <--> AL
EF <--> GPT
PA --> GPT
VP --> GPT
EF --> TW
EF --> EM
Frontend <--> EF
SQ --> EF
| Layer | Technology |
|---|---|
| Backend | Supabase Edge Functions (Deno/TypeScript) |
| Database | PostgreSQL (Supabase) |
| Frontend | React, Vite, Tailwind CSS |
| AI | OpenAI GPT-4 API |
| Automation | Playwright (headless browser) |
| Channel Manager | Channel Manager API + Webhooks |
| SMS | Twilio |
| POS | Square API |
| Hosting | Netlify (frontend), Supabase (backend) |
| CI/CD | Custom deploy scripts, UAT → Prod promotion |
The heart of it is a daily pricing pipeline: a chain of about six Supabase Edge Functions that run in dependency order. Each declares its predecessors, and the scheduler holds a function until its upstream steps finish, so a slow or failed step never lets a downstream one run on stale data. Webhook ingestion for bookings and cafe sales and the APIs that serve the dashboard run as their own functions alongside it.
GPT-4-powered analysis that evaluates booking pace, occupancy velocity, seasonal patterns, and local events across all properties. Generates specific rate recommendations with reasoning, routed to the owner for approval before any changes go live.
Rates sync to Airbnb, Booking.com, VRBO, and a direct booking site. The channel manager API handles most platforms; VRBO requires browser automation due to API limitations in the channel manager.
Cafe revenue data from Square feeds into property analytics, giving a fuller picture of the business operation.
The webhook chain, dead letter queue, advisory locks, owner approval gate, and VRBO automation each get a full walkthrough in the next section.
The business logic is mine and it stays private. The engineering is the part worth talking about. Most of this I built because something broke and I didn't want it breaking again, not because I planned it out up front.
I started simple. Six cron jobs at six staggered times, spaced out enough that each one should finish before the next kicks off. Worked fine until the channel manager API had a slow morning, the sync step ran long, and gap detection started reading data that was only half written. I pushed rates off stale state and didn't notice until that night.
So now every function calls waitForPredecessors() when it starts and waits on an execution-log table until the steps it depends on have actually finished for that run. If something upstream dies or hangs, the function downstream just stops instead of guessing. It is more plumbing than I wanted, but pricing on stale data is the one thing I am not willing to let happen.
Pushes fail for two reasons that look the same and need the opposite treatment. A rate limit or a dropped connection should get retried. An invalid rate or a delisted property is never going to work, and pounding on it just hides the failures I actually need to see.
So every failure gets tagged transient or permanent on the way into the queue. Transient retries with backoff and usually fixes itself. Permanent pages me. Retry hard on flaky infrastructure, do not waste time on data that is just broken.
This was a bug before it was a decision. A cron job and a booking webhook both went to reprice the same property in the same second, read the same starting numbers, came up with different rates, and the slower one overwrote the better answer. Happened once, couldn't reproduce it, which is the kind of bug I hate most.
Advisory locks killed it. I lock per property instead of the whole table, so two different properties still reprice at the same time, but two writes to the same one have to take turns.
One booking sets off a chain: reprice the property, look for gaps the booking just created, adjust the dates around nearby events. Webhooks get redelivered, steps get retried, so any link can fire twice. If those steps weren't idempotent you'd get double adjustments and rates that slowly drift off. Every step is safe to run more than once and retries on its own, so if something dies halfway through, it just replays clean instead of leaving a property half done.
The advisor is good and I still don't let it touch live rates. It writes a proposal with its reasoning to a table and that's where it sits until I approve it from the dashboard. A bad model run is a suggestion I say no to, not a rate a guest actually sees. This moves real money, so I wanted one person on the hook for every change. That's me.
Everything else goes through the channel manager API. VRBO doesn't, because the channel manager can't push rates to it. I wasn't going to drop a whole channel over that, so there's a Playwright script that logs into the host dashboard, sets the rates, and then reads them back to make sure it took. It's the ugliest piece of the whole thing and I kept it on purpose. Every real setup has one integration that won't play nice. The job was hiding it behind the same rate-push interface as everything else so the rest of the code never has to care that VRBO is the problem child.
- 6 properties under active management
- Dependency-ordered daily pricing pipeline (sync, gap detection, event pricing, AI advisor, rate push)
- 4 distribution channels synced
- ~2 years in production
- 44% ahead of historical booking pace since launch
- Zero manual spreadsheet pricing since launch
Screenshots of the dashboard, pricing proposals, and analytics views are available on request during interviews. They are not included here to protect guest data and proprietary rate information.
str-pricing-engine/
README.md # This file
docs/
ARCHITECTURE.md # System design deep dive
TECHNICAL_DECISIONS.md # Engineering tradeoffs
samples/
webhook-chain.ts # Webhook processing pattern
cron-dependency-system.ts # Cron execution ordering
dead-letter-queue.ts # DLQ with retry logic
browser-automation.ts # Playwright VRBO sync
ai-advisor-overview.ts # AI decision flow (pseudocode)
Built and operated by a solo developer managing a real vacation rental business. Every design decision was driven by an actual operational problem -- not theoretical best practices. The system runs 24/7 and directly impacts revenue.