How we built PropertyTools.ai from zero to production
PropertyTools.ai is the AI-powered property management platform we built for landlords and small property investors who don't want to wrestle with enterprise software designed for 500-unit portfolios. This post walks through the architectural decisions that got it from a Notion document to a live, paying-customer SaaS, plus the ones we got wrong the first time and had to redo.
The problem we were solving
Most property management software is built for property management companies: firms managing dozens of buildings on behalf of owners. The UI assumes you have an office staff, a maintenance vendor network, and a workflow built around batch operations. Independent landlords with 1 to 10 units don't fit any of those assumptions. They live in spreadsheets and personal email, miss rent dates, lose receipts, and end up doing taxes from a shoebox.
PropertyTools.ai started as the question: what if the software treated a single landlord like the primary user, and used AI to do the boring work (categorizing transactions, drafting tenant emails, summarizing leases) instead of asking the landlord to learn a new piece of enterprise software?
The stack
We picked the stack we ship most projects on, deliberately. There were no points to score by being clever:
- Next.js (App Router) on Vercel for the web application. Server Components for the data-heavy dashboard pages, client components only where interactivity demanded it.
- PostgreSQL on Neon for primary storage, with row-level security as the multi-tenancy boundary.
- Drizzle ORMfor typed queries. We've used Prisma on other projects. Drizzle won this one because of the generated SQL transparency and how cleanly it handles the RLS policies we use for tenant isolation.
- Anthropic Claude for every AI-powered feature. (More on the integration patterns in a separate post on using Claude in production.)
- Stripe for billing and rent collection.
- Inngest for background jobs: rent reminders, AI batch processing, monthly statement generation.
- Sentry + Axiom for error tracking and structured logging.
The multi-tenancy decision
Multi-tenancy is the architectural decision that has the longest tail. Get it wrong on day one and you spend a year refactoring after the first real customer. We had three options: shared schema with a tenant_id column on every table, schema-per-tenant in PostgreSQL, or database-per-tenant.
We picked shared schema with row-level security policies enforced at the database level. RLS means even if our application code has a bug (say, a query that forgets the WHERE tenant_id = clause), the database itself blocks the leak. Postgres won't return rows the current session role isn't allowed to see. For a SaaS that handles bank account numbers and lease agreements, that defense in depth was worth the slight added complexity.
(We made a different decision for BlueGrasshopper, which is a financial advisory platform. There we used schema-based isolation. The tradeoffs were different and worth their own post.)
Where AI actually earned its keep
We were deliberate about not bolting AI onto every screen. The places AI made the product meaningfully better:
- Transaction categorization.Bank-import shows up as an unstructured CSV with cryptic merchant names. Claude tags each row with category (rent, mortgage, repair, utility) and a confidence score. We display the suggestion, the user clicks to accept, and the category sticks for similar future transactions. Accuracy was 87% on our launch dataset and the user-correction loop pushed it past 95% within the first month of any landlord's usage.
- Lease summarization. Upload a 14-page PDF lease, get a structured summary: tenant names, rent amount, deposit, duration, special clauses (pets, smoking, late fees). The structured output drives the calendar reminders and the dashboard.
- Tenant-email drafting.Pick the situation (late rent, lease renewal, maintenance follow-up), and Claude drafts a firm-but-respectful email tailored to the tenant's record. The landlord edits before sending. We never auto-send.
Where we deliberately didn't use AI: anywhere the wrong answer could cost the user money or expose them legally. We don't auto-categorize transactions without confirmation, don't generate legal notices, and don't evaluate tenant applications. Those are the user's calls.
What we got wrong the first time
Two big things.
We over-built the AI batch pipeline.The first version used a queue, a worker process, retries with exponential backoff, dead letter queues. The whole thing. Customer #4 was a guy with one rental property uploading one bank statement a month. The queue infrastructure was costing us more than we were charging him. We ripped most of it out and went back to direct API calls inside the request handler for anything under 30 seconds. Inngest handles the actually-batch work (monthly statements, large CSV imports). Lesson: match the infrastructure to the actual workload, not the workload you imagine you'll have at scale.
We rolled our own auth.We thought it would be one file. It turned into magic-link delivery, session management, organization invites, password fallback, account recovery. Three weeks of engineering we should have bought. We migrated to a managed auth provider in v2. If you're building a SaaS, do not roll auth. Buy it.
Where we are now
PropertyTools.ai ran in production serving independent landlords and small property investors. The hosting is currently down and we haven't brought it back up, so treat everything above as a write-up of a system that shipped and ran, not a pitch for something you can sign up for today. The architecture notes stand on their own either way.
If you're building a vertical SaaS where AI is meant to remove the boring work for users who hate software, the patterns above translate. Start with a sturdy foundation, only add AI where it actually earns its keep, and build the user-correction loop into the product from day one. The model gets smarter with usage, but only if you let users teach it.
Want a system like this for your own domain? Talk to us.