Files
property-management-network/README.md
T

230 lines
7.7 KiB
Markdown
Raw Normal View History

<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="public/logo-light.svg">
<img alt="Property Management Network" src="public/logo-dark.svg" width="360">
</picture>
</p>
# Property Management Network
**Property management SaaS for independent landlords.** Track properties, tenants, rent, maintenance, leases, and expenses — all in one clean dashboard.
Built with Next.js 16, PostgreSQL (Drizzle ORM), Better Auth, Stripe, and OpenAI. Ready to deploy on Vercel in under 10 minutes.
---
## What it does
Property Management Network replaces the spreadsheet + WhatsApp chaos that most small landlords live with. Key capabilities:
- **Properties & units** — manage your entire portfolio with occupancy tracking
- **Tenant profiles** — contact info, lease history, payment records, and a private tenant portal
- **Rent tracking** — log payments, send Stripe payment links, auto-mark overdue balances
- **Maintenance requests** — status workflow (Open → In Progress → Resolved), tenant submissions via portal
- **Lease management** — expiry countdowns, automated 60/30/7-day email alerts
- **Expenses** — categorized logging with recurring expense support
- **Documents** — file vault per property with drag-and-drop upload to local disk, served through an auth-gated route
- **AI features** — AI-powered recommendations, predictions, and impact tracking (Pro+)
- **Automated emails** — rent reminders, overdue alerts, lease expiry notifications via Resend
- **Tenant portal** — token-based (no login), tenants can view rent history and submit maintenance
---
## Revenue model
| Plan | Price | Limits |
|------|-------|--------|
| Starter | Free | 1 property, 3 tenants, no AI |
| Pro | $29/mo | 10 properties, unlimited tenants, AI (50 calls/mo) |
| Landlord | $59/mo | Unlimited properties, team access, white-label, AI (200/mo) |
| Lifetime | $199 one-time | Everything in Landlord, forever |
Subscription billing via Stripe. Lifetime deal is ideal for Flippa buyers who want to offer an LTD to early customers.
---
## Tech stack
| Layer | Tech |
|-------|------|
| Framework | Next.js 16.2 (App Router, TypeScript) |
| Styling | Tailwind CSS + Geist font |
| Database | PostgreSQL (via Drizzle ORM) |
| Auth | Better Auth (email/password + Google OAuth) |
| Storage | Local disk (auth-gated file serving) |
| Payments | Stripe (subscriptions + payment links) |
| AI | OpenAI (gpt-4o-mini) |
| Email | Resend |
| Cron | Vercel Cron Jobs |
| Deploy | Vercel |
---
## Setup
### 1. Clone and install
```bash
git clone <your-repo>
cd property-management-network
npm install
```
### 2. Configure environment variables
```bash
cp .env.example .env.local
```
Fill in `.env.local`:
```env
# Database (PostgreSQL via Drizzle ORM)
DATABASE_URL=
# Auth (Better Auth)
BETTER_AUTH_URL=http://localhost:3000
BETTER_AUTH_SECRET=your-random-secret-string
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
# File storage (local disk)
STORAGE_DIR=./storage
# Stripe
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
STRIPE_PRO_MONTHLY_PRICE_ID=
STRIPE_LANDLORD_MONTHLY_PRICE_ID=
STRIPE_LIFETIME_PRICE_ID=
# OpenAI
OPENAI_API_KEY=
# Resend
RESEND_API_KEY=
RESEND_FROM_EMAIL=Property Management Network <noreply@yourdomain.com>
# App
NEXT_PUBLIC_APP_URL=http://localhost:3000
CRON_SECRET=your-random-secret-string
```
### 3. Run database migrations
The schema is managed with Drizzle ORM (see `drizzle.config.ts`). Point `DATABASE_URL` at your PostgreSQL instance in `.env.local`, then apply the migrations from `lib/db/migrations`:
```bash
npm run db:migrate
```
To regenerate migrations after changing the schema, use `npm run db:generate`. For quick local prototyping you can push the schema directly with `npm run db:push`.
### 4. Configure Stripe
Create three products in your Stripe dashboard:
- **Pro Monthly** — $29/mo recurring → copy Price ID to `STRIPE_PRO_MONTHLY_PRICE_ID`
- **Landlord Monthly** — $59/mo recurring → copy Price ID to `STRIPE_LANDLORD_MONTHLY_PRICE_ID`
- **Lifetime** — $199 one-time → copy Price ID to `STRIPE_LIFETIME_PRICE_ID`
Set up a webhook at `https://yourdomain.com/api/stripe/webhook` listening to:
- `checkout.session.completed`
- `customer.subscription.created`
- `customer.subscription.updated`
- `customer.subscription.deleted`
- `invoice.payment_failed`
- `payment_intent.succeeded`
### 5. Configure Resend
Add a verified sending domain in your Resend dashboard. Update `RESEND_FROM_EMAIL` with your domain address.
### 6. (Optional) Google OAuth
Create OAuth credentials in the Google Cloud Console and set `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` to enable Google sign-in via Better Auth.
### 7. Run locally
```bash
npm run dev
```
Open [http://localhost:3000](http://localhost:3000).
### 8. Deploy to Vercel
Connect the repo in the Vercel dashboard and add all environment variables under **Settings → Environment Variables**.
Cron jobs are pre-configured in `vercel.json` and run automatically on Vercel.
---
## Project structure
```
app/
├── (marketing)/ # Landing page, pricing, legal
├── (auth)/ # Login, signup, password reset
├── (dashboard)/ # All dashboard pages (auth-gated)
│ ├── dashboard/ # Overview + stats
│ ├── properties/ # Property + unit management
│ ├── tenants/ # Tenant profiles
│ ├── rent/ # Payment tracking
│ ├── maintenance/ # Maintenance requests
│ ├── leases/ # Lease tracking
│ ├── expenses/ # Expense logging
│ └── settings/ # Billing + profile
├── api/
│ ├── properties/ # CRUD
│ ├── tenants/ # CRUD + auto unit assignment
│ ├── rent/ # CRUD + Stripe payment links
│ ├── maintenance/ # CRUD + status workflow
│ ├── leases/ # CRUD
│ ├── expenses/ # CRUD
│ ├── documents/ # Document metadata (files on local disk)
│ ├── ai/ # Rent receipts + maintenance summaries
│ ├── notifications/ # Send emails via Resend
│ ├── stripe/ # Checkout, portal, webhook
│ └── cron/ # Rent reminders + lease expiry alerts
└── tenant-portal/[token]/ # Public tenant portal (no login)
lib/
├── db/ # Drizzle schema, queries, migrations
├── auth.ts # Better Auth config
├── storage.ts # Local-disk file storage helpers
├── stripe/ # Client, plans, payment links
├── ai/ # OpenAI client + prompts
├── email/ # Resend client + HTML templates
└── validations/ # Zod schemas for all entities
drizzle.config.ts # Drizzle ORM config (DATABASE_URL, migrations dir)
```
---
## Database schema
11 tables, managed via Drizzle ORM:
`profiles` · `properties` · `units` · `tenants` · `rent_payments` · `maintenance_requests` · `leases` · `expenses` · `documents` · `notifications` · `usage_events`
Data isolation is enforced in the application layer: every API route authenticates via `getSessionUser()` and scopes its queries by `user_id`. There is no database-level RLS, so this query scoping must be maintained carefully on every new route and query.
---
## Cron jobs
| Job | Schedule | What it does |
|-----|----------|--------------|
| Rent reminders | Daily 9am UTC | Marks overdue payments, sends 3-day reminder emails |
| Lease expiry | Daily 10am UTC | Sends 60/30/7-day expiry alerts to landlord |
Cron routes are protected with `CRON_SECRET` (Bearer token in `Authorization` header).
---
## License
MIT