Property Management Network

# 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. Deploys to DigitalOcean App Platform (see [DIGITALOCEAN.md](DIGITALOCEAN.md)). --- ## 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 SMTP (SMTP2GO) - **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 | DigitalOcean Spaces (S3-compatible, CDN, auth-gated) | | Payments | Stripe (subscriptions + payment links) | | AI | OpenAI (gpt-4o-mini) | | Email | SMTP (SMTP2GO) | | Cron | DigitalOcean Functions (scheduled triggers) | | Deploy | DigitalOcean App Platform (Docker image via DOCR) | --- ## Setup ### 1. Clone and install ```bash git clone 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 (no price IDs needed — resolved by lookup key, auto-created on first checkout) STRIPE_SECRET_KEY= STRIPE_WEBHOOK_SECRET= NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY= # OpenAI OPENAI_API_KEY= # Email (SMTP — e.g. SMTP2GO) SMTP_HOST=mail.smtp2go.com SMTP_PORT=2525 SMTP_USER= SMTP_PASS= EMAIL_FROM=postmaster@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 Add your API keys (`STRIPE_SECRET_KEY`, `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY`) — that's it. Products and prices are resolved by stable **lookup keys** and auto-created on first checkout (Pro $29/mo, Landlord $59/mo, Lifetime $199, plus annual), so there are **no price IDs to configure** and going live is just an API-key swap. To pre-create the catalog, optionally run `node scripts/stripe-setup.mjs`. 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 email (SMTP) Use any SMTP provider (e.g. SMTP2GO). Verify your sending domain with the provider, then set `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`, and `EMAIL_FROM`. ### 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 (DigitalOcean App Platform) The repo ships a production `Dockerfile` (Next.js standalone output), an App Platform spec at [`.do/app.yaml`](.do/app.yaml), DO Functions cron under [`functions/`](functions/), and a `/api/health` liveness probe. See **[DIGITALOCEAN.md](DIGITALOCEAN.md)** for the full walkthrough: build/push the image to DOCR, create the app, wire up Managed Postgres + Spaces, and deploy the scheduled cron functions. --- ## 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 SMTP (SMTP2GO) │ ├── 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/ # SMTP (SMTP2GO) 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