Property showing operations platform

Delet Admin

Full-stack engineer · March 2024 – August 2026 · sole or lead engineer throughout

Delet let renters tour a property without a leasing agent present: book a slot online, verify identity, unlock the door with a time-limited code. I owned the platform that made that run, and rebuilt it from an undocumented legacy system into the stack the company runs on today.

Product site
Delet Admin project preview
  • ~90%lower monthly infra costRoughly $2–3K/mo on hand-managed AWS down to about $200/mo on Railway (my estimate, not an audited figure).
  • 10×faster admin queriesTen seconds and occasional timeouts, down to about one second after the PostgreSQL re-model.
  • faster deploys, with checks addedA 40–60 minute manual laptop build and FTP upload replaced by a 10–20 minute GitHub Actions pipeline into Railway, one that runs tests and checks the old path had none of.
  • 0wrong invoices per cycleDown from a few every billing cycle, after the day-before verification step.

The problem

Leasing a rental unit traditionally requires an agent physically present for every tour. That caps how many showings a property can run in a day, pushes prospects into weekday business hours, and keeps units vacant longer. Vacancy is the cost a property manager is measured on.

Delet’s answer was the self-guided showing: a prospect books a slot, verifies their identity, and unlocks the door themselves with a time-limited code. The company positions the model on delivering roughly 500% more showings than in-person tours.

Making that work is an operations problem more than a real-estate one. Behind every self-guided tour sits scheduling, identity verification, access codes that issue and expire on time, smart-lock hardware installed at the property, and an audit trail of who entered which unit and when. When I joined, much of that ran on manual coordination across tools and spreadsheets.

The hardware was the clearest example. Delet ships physical smart-lock kits to client properties, and tracking which client had which kit was done by hand. Kits were invoiced manually, and when a client returned one, someone had to remember to take it off the billing cycle. When they forgot, Delet billed customers for hardware they had already sent back.

Three audiences depended on it: leasing staff running properties and bookings, renters touring unaccompanied, and Delet’s own ops team running showings on behalf of client companies.

My role

I joined in March 2024 as the only engineer on a live product with paying customers, and no one from the original team was left to explain it. There were no documents and no handoff, so the first job was reverse-engineering a system that was already in production.

The team never exceeded two engineers. Over the next two years a frontend developer was hired and left, two more joined and left, and eventually a steady engineering partner and a CTO came on. I was the one who stayed, which made me the technical owner of the platform whatever my title said.

  • Live product with paying customers: no greenfield, no downtime budget
  • Never more than two engineers, and long stretches solo
  • No original authors, no documentation, no handoff
  • No dedicated DevOps, designer, or QA
  • Stakeholders who couldn’t always specify what they wanted

Approach

Rebuild rather than repair

The inherited stack was a React SPA, an Express API, and MongoDB on AWS, with Cognito for auth, S3 for storage, and a single EC2 instance hosting both tiers. Neither tier had enforced structure. Every developer who passed through had invented their own patterns, which is survivable in a stable team and corrosive in one that turns over every few months. The call was that repairing each tier in place would cost more than moving it, and that moving to a conventional framework would sweep up the backlog of fixes on the way. I optimized for convention over flexibility, because onboarding time was the real constraint.

Express to NestJS, AI-assisted

NestJS’s modules and dependency injection impose boundaries a rotating team follows without being told. I used AI tooling to accelerate the port itself, so the migration and the accumulated fixes landed in one pass instead of two.

Bootstrap to Tailwind

The frontend used Bootstrap, which meant every visual change fought the library instead of using it. Tailwind made restyling cheap, which mattered, because the admin UI needed a lot of it.

EC2 and FileZilla to Railway and GitHub Actions

Deploys were manual: I built the application on my own laptop and uploaded it to the EC2 box over FTP. Nothing tested or checked the code on its way to production, and shipping depended on my specific machine being available. Railway with GitHub Actions traded some infrastructure control for what a one-to-two person team needed more: automated deploys, per-PR preview environments, and no server to babysit.

MongoDB to PostgreSQL, last and hardest

Delet’s data is deeply interconnected: users, companies, properties, units, bookings, leads, kits, subscriptions. It had been modeled as documents. The duplication that entails is tolerable until it isn’t, and the forcing function was performance: some admin queries took ten seconds or more, and some timed out. A relational model was the right answer and had been from the start. Getting there meant moving live production data with no acceptable loss, so I wrote a custom migration tool in Rust to do the transformation. A full run took hours, so I put an AI agent in the loop to watch each run, surface errors, apply fixes, and start the next pass. That turned an overnight babysitting job into something that could iterate its way to correctness.

What changed, layer by layer
LayerInheritedRebuilt
FrontendReact SPA, no structureNext.js
StylingBootstrapTailwind CSS
APIExpress, no enforced structureNestJS, layered modules
DatabaseMongoDBPostgreSQL, normalized
AuthAWS CognitoAWS Cognitodeliberately unchanged
HostingOne EC2 box, both tiersRailway
DeployLocal build, FTP uploadGitHub Actions CI/CD
ObservabilityNonePostHog, logs, Slack alerts

Authentication stayed on AWS Cognito throughout; there was no reason to take on that risk alongside everything else.

What went wrong

Migrating under live customers

The migration ran underneath paying customers and finished without downtime, but I want to be accurate about what that cost. We knew going in that a system with no test coverage would have behaviors we’d fail to reproduce, and that clients would be the ones to find them. That was an accepted trade rather than an oversight, and it’s the decision I’d make differently.

The billing spec nobody could write down

Automating kit billing against QuickBooks was the hardest problem in the project, and almost none of the difficulty was code. The people who owned the process couldn’t articulate how they wanted it to work. What surfaced while trying to pin it down was that the process itself was broken in a way automation would have made worse: taking a returned kit off the billing cycle was a manual step, and when it was missed, Delet invoiced clients for hardware they’d sent back. It happened a few times every cycle. Automating that faithfully would have industrialized the error, so I pushed back on “automate the current process” and proposed a checkpoint instead. The system now determines which kits are due to be charged and surfaces that list a day before the charge runs, so ops can verify ownership while there’s still time to fix it. The daily job handles the volume; a person handles the judgment. The wrong invoices stopped.

Results

Delet runs on that rebuilt stack today: Next.js, NestJS, PostgreSQL, Railway, GitHub Actions. I left in August 2026 and it is still running on it.

What I’d do differently

  • Migrate the database first, not last. PostgreSQL was the hardest piece and it came last, so earlier work was built on a data model I already knew was wrong.
  • Write characterization tests before migrating, not after. The gaps clients reported were findable internally. We just had nothing to find them with.
  • Force the billing spec sooner. I spent real time waiting on a requirement that was never going to arrive in words. Proposing the day-before checkpoint earlier would have saved cycles.

Stack

  • Next.js
  • NestJS
  • PostgreSQL
  • Railway
  • GitHub Actions
  • PostHog