Files
property-management-network/README.md
T
Leon SerfatyandClaude Opus 4.8 857b9a7811 Initial import: property management SaaS + security hardening + admin dashboard
Property Management Network — Next.js 16 (App Router), Better Auth,
Drizzle ORM over PostgreSQL, Stripe, OpenAI, Resend.

Includes:
- Security hardening: access-control/IDOR fixes, TLS-by-default DB layer,
  constant-time cron auth, strict security headers, atomic AI quota gating,
  HTML/email output encoding, demo-backdoor disabled in production.
- Superadmin dashboard at /admin (overview/MRR, server-paginated users with
  ban/impersonate/plan/delete, billing, platform activity + admin audit log,
  AI usage, system health) via the Better Auth admin plugin.
- Seed/migration utility scripts under scripts/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:36:07 -04:00

7.7 KiB

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. 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

git clone <your-repo>
cd property-management-network
npm install

2. Configure environment variables

cp .env.example .env.local

Fill in .env.local:

# 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:

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

npm run dev

Open 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