A production-ready, multi-tenant SaaS CRM for home cleaning businesses. Built with Next.js 14, TypeScript, Prisma, and PostgreSQL.
- Each cleaning company is its own isolated workspace
- Complete data isolation between tenants
- Custom branding per workspace
- Admin/Owner: Full access to all features
- Dispatcher: Manage bookings, customers, and cleaners
- Cleaner: View assigned jobs, update checklists, track time
- Customer: Self-service booking and payment portal
- 📅 Booking Management: Create, schedule, and track cleaning jobs
- 👥 Customer Management: Complete CRM with addresses, notes, and tags
- 🧹 Cleaner Management: Availability, assignments, time tracking
- 💳 Payment Processing: Stripe integration with deposits and invoices
- 📲 Messaging: Automated SMS and email notifications
- 🔄 Recurring Schedules: Weekly, biweekly, monthly recurring jobs
- ✅ Checklists: Service-specific task lists
- 📊 Reporting: Revenue, utilization, and performance metrics
- 🤖 Automations: Booking confirmations, reminders, review requests
- Frontend: Next.js 14 (App Router), React, TypeScript, Tailwind CSS
- Backend: Next.js API Routes
- Database: PostgreSQL with Prisma ORM
- Authentication: NextAuth.js
- Payments: Stripe
- SMS: Twilio
- Email: Postmark/SendGrid
- File Storage: AWS S3
- Deployment: Vercel (recommended)
- Node.js 18+
- PostgreSQL 14+
- Stripe account
- Twilio account (for SMS)
- Postmark or SendGrid account (for email)
- AWS S3 bucket (for file uploads)
git clone <your-repo-url>
cd cleanpro
npm installCopy .env.example to .env:
cp .env.example .envEdit .env with your credentials:
# Database
DATABASE_URL="postgresql://user:password@localhost:5432/cleanpro"
# NextAuth
NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_SECRET="<generate with: openssl rand -base64 32>"
# Stripe
STRIPE_SECRET_KEY="sk_test_..."
STRIPE_PUBLISHABLE_KEY="pk_test_..."
STRIPE_WEBHOOK_SECRET="whsec_..."
# Twilio
TWILIO_ACCOUNT_SID="AC..."
TWILIO_AUTH_TOKEN="..."
TWILIO_PHONE_NUMBER="+1234567890"
# Email (Postmark)
POSTMARK_API_TOKEN="..."
POSTMARK_FROM_EMAIL="noreply@yourdomain.com"
# AWS S3
AWS_ACCESS_KEY_ID="AKIA..."
AWS_SECRET_ACCESS_KEY="..."
AWS_REGION="us-east-1"
AWS_S3_BUCKET="cleanpro-files"
# App
NEXT_PUBLIC_APP_URL="http://localhost:3000"
CRON_SECRET="<random-secret>"# Run migrations
npm run db:migrate
# (Optional) Seed test data
npm run db:seednpm run devVisit http://localhost:3000
The application uses a comprehensive multi-tenant schema. See prisma/schema.prisma for details.
- Workspace: Tenant isolation
- User: Authentication (linked to role-specific profiles)
- Customer: Customer profiles with addresses
- Cleaner: Employee profiles with availability
- Booking: Jobs/appointments
- Service: Cleaning service definitions
- Invoice: Payment tracking
- Message: Communication logs
- Automation: Automation rules
See API_ROUTES.md for complete API reference.
POST /api/auth/signup - Create workspace + admin
POST /api/auth/signin - Login
GET /api/customers - List customers
POST /api/customers - Create customer
GET /api/bookings - List bookings
POST /api/bookings - Create booking
GET /api/calendar - Calendar view
GET /api/cleaners/jobs - Cleaner's jobs
POST /api/payments/create-intent - Create payment
POST /api/webhooks/stripe - Stripe webhooks
See PROJECT_STRUCTURE.md for detailed file organization.
src/
├── app/ # Next.js App Router
│ ├── (auth)/ # Login/signup pages
│ ├── (dashboard)/ # Admin interface
│ ├── (customer)/ # Customer portal
│ ├── (cleaner)/ # Cleaner app
│ └── api/ # API routes
├── components/ # React components
├── lib/
│ ├── auth/ # Authentication
│ ├── services/ # Business logic
│ ├── integrations/ # External APIs
│ └── validators/ # Zod schemas
└── types/ # TypeScript types
- Signup: Creates workspace + admin user
- Login: Returns NextAuth session with workspace context
- Middleware: Injects
workspaceIdinto all requests - RBAC: Permissions checked on every API call
Every database query is automatically scoped to the user's workspace:
// Middleware injects workspaceId
const customers = await prisma.customer.findMany({
where: { workspaceId } // Always filtered by workspace
})- Push to GitHub
- Import to Vercel
- Add environment variables
- Deploy!
Vercel will automatically:
- Run database migrations
- Set up serverless functions
- Configure edge caching
Set all variables from .env.example in your deployment platform.
Important:
- Use production Stripe keys
- Set
NEXTAUTH_URLto your domain - Configure Stripe webhook endpoint:
https://yourdomain.com/api/webhooks/stripe
Production migrations:
npm run db:migrate:deploy- Dashboard → Developers → API Keys
- Copy Secret Key and Publishable Key
- Dashboard → Developers → Webhooks → Add endpoint
- URL:
https://yourdomain.com/api/webhooks/stripe - Events:
payment_intent.succeeded,payment_intent.payment_failed,charge.refunded - Copy webhook secret to
STRIPE_WEBHOOK_SECRET
Use test keys (sk_test_...) for development. Switch to live keys (sk_live_...) for production.
- Create account at twilio.com
- Get phone number
- Copy Account SID, Auth Token, and Phone Number
- Add to
.env
- Create account at postmarkapp.com
- Add and verify sender signature
- Get Server API Token
- Add to
.env
- Create S3 bucket
- Create IAM user with S3 access
- Generate access keys
- Configure bucket CORS:
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "PUT", "POST"],
"AllowedOrigins": ["http://localhost:3000", "https://yourdomain.com"],
"ExposeHeaders": ["ETag"]
}
]Automations run via Vercel Cron or similar:
- Daily 7am: Send cleaner schedules
- Hourly: Check for reminders
- Every 15min: Process follow-ups
- Daily: Generate upcoming recurring jobs
Example cron configuration (vercel.json):
{
"crons": [
{
"path": "/api/cron/daily",
"schedule": "0 7 * * *"
},
{
"path": "/api/cron/hourly",
"schedule": "0 * * * *"
}
]
}- ✅ Password hashing with bcrypt
- ✅ CSRF protection (NextAuth)
- ✅ SQL injection protection (Prisma)
- ✅ XSS protection (React auto-escaping)
- ✅ Multi-tenant data isolation
- ✅ Role-based access control
- ✅ Stripe webhook signature verification
- ✅ Input validation (Zod)
npm run dev # Start dev server
npm run build # Production build
npm run start # Start production server
npm run lint # Run ESLint
npm run type-check # TypeScript check
npm run db:migrate # Run migrations (dev)
npm run db:migrate:deploy # Run migrations (prod)
npm run db:studio # Open Prisma Studio
npm run db:seed # Seed test data- Create workspace:
POST /api/auth/signup - Login: Visit
/login - Create customer:
/customers/new - Create booking:
/bookings/new - View calendar:
/calendar
- Success:
4242 4242 4242 4242 - Decline:
4000 0000 0000 0002 - Any future expiry, any CVC
# Check DATABASE_URL format
# postgresql://user:password@host:port/database
# Test connection
npx prisma db pull# Regenerate Prisma Client
npx prisma generate- Verify webhook secret matches Stripe dashboard
- Check endpoint URL is correct
- Ensure no trailing slash
- Verify Twilio credentials
- Check phone number format (+1234567890)
- Verify Twilio account is active
- Set all environment variables
- Use production API keys (Stripe, Twilio, etc.)
- Configure custom domain
- Set up Stripe webhook endpoint
- Test payment flow end-to-end
- Configure email sender domain
- Set up database backups
- Enable error monitoring (Sentry)
- Test all user roles
- Configure CORS for S3 bucket
- Set up cron jobs for automations
- Database indexes on
workspaceId,scheduledDate,status - Next.js image optimization
- API route caching where appropriate
- Prisma query optimization
- CDN for static assets
- ✅ Multi-tenant architecture
- ✅ Booking management
- ✅ Payment processing
- ✅ Messaging & automations
- ✅ Basic reporting
- Google Calendar sync
- QuickBooks integration
- Two-cleaner team assignments
- Advanced reporting dashboard
- Mobile apps (React Native)
- Online booking widget for websites
- Customer reviews & ratings
- Inventory management
- Route optimization
For issues, please check:
- This README
ARCHITECTURE.mdfor system designAPI_ROUTES.mdfor API documentation- GitHub issues (if public repo)
MIT License - See LICENSE file for details
- Fork the repository
- Create feature branch (
git checkout -b feature/amazing-feature) - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open Pull Request
Built with ❤️ for cleaning businesses worldwide