Production-ready, mobile-first real-estate CRM. Built with Next.js 15, TypeScript, Tailwind CSS, shadcn/ui and Supabase. Designed for real estate teams who get leads from 36 Acre, MagicBricks, Housing.com, Facebook / Instagram Ads, WhatsApp and their own website — and need to call them in seconds, send property details in one tap, and track every interaction.
- Instant agent-to-lead bridge call — Twilio Voice rings the assigned agent first, then dials the lead and bridges them into a conference. Falls back to the next available agent if the first one doesn't answer.
- Webhook lead intake with HMAC-SHA256 signature verification, round-robin / least-busy assignment and instant in-app notification.
- One-click property photo sharing over WhatsApp / SMS / email with public share links.
- Follow-up automation with templates, schedule, snooze and due notifications.
- Inventory with photos, documents, availability state and matching to leads by budget, location and type.
- Attendance with GPS, optional selfies and admin dashboard.
- Social media calendar with idea / draft / scheduled / published states and AI caption helper.
- Reports & dashboard — leads by source, agent call performance, follow-ups, shares, attendance summary.
- Multi-tenant with Row Level Security — every table is scoped by
organization_idand protected with RLS policies. - Dry-run mode for Twilio, WhatsApp, SMS and email so the whole flow can be exercised locally without spending a rupee.
| Layer | Choice |
|---|---|
| Frontend | Next.js 15 (App Router), TypeScript, Tailwind CSS |
| UI kit | shadcn/ui + @base-ui/react |
| Backend | Next.js API routes + Server Actions |
| Database | Supabase Postgres with RLS |
| Auth | Supabase Auth |
| Storage | Supabase Storage (property photos, documents) |
| Voice | Twilio Voice (with TwiML & conference bridge) |
| Messaging | Twilio WhatsApp / SMS adapter |
| Resend (preferred) or SMTP adapter | |
| AI | OpenAI-compatible adapter (caption / copy draft) |
| Hosting | Vercel (frontend) + Supabase (backend) |
estateflow-crm/
├── src/
│ ├── app/
│ │ ├── (app)/ # Authenticated app pages (mobile shell, dashboard, leads, …)
│ │ ├── api/ # Route handlers (REST + webhooks)
│ │ │ ├── webhooks/leads # External lead intake
│ │ │ └── twilio/voice/* # Twilio voice webhooks
│ │ ├── share/property/[slug] # Public property share page
│ │ ├── login/ signup/ # Auth pages
│ ├── components/ # UI components (shared, leads, properties, …)
│ ├── lib/
│ │ ├── db/queries.ts # Typed Supabase queries
│ │ ├── services/ # callService, messageService, emailService, …
│ │ ├── supabase/ # Browser + server + service-role clients
│ │ ├── types/database.ts # Schema-mirroring TS types
│ │ └── validations/ # Zod schemas
│ └── …
├── supabase/
│ ├── migrations/ # Schema + RLS policies
│ └── seed/ # Seed SQL (optional)
├── scripts/
│ └── seed.ts # Programmatic seed (recommended)
└── .env.example
The service layer (src/lib/services/*) is deliberately decoupled from UI —
no Twilio / WhatsApp / email calls live in components. Every service supports
production mode and dry-run mode, switched per organization via
integration_settings.dry_run_mode.
- Node.js 22.x and npm 10.x
- A free Supabase project (https://app.supabase.com)
- Optional for live voice: a Twilio account with a phone number
- Optional for live email: a Resend account
git clone https://github.com/<you>/estateflow-crm.git
cd estateflow-crm
npm installIn your Supabase project SQL editor, run the migration files in order:
supabase/migrations/20260518000000_initial_schema.sql
supabase/migrations/20260518000001_rls_policies.sql
(or use the Supabase CLI: supabase db push)
cp .env.example .env.local
# Fill in NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY,
# SUPABASE_SERVICE_ROLE_KEY at a minimum.npm run seedThis creates:
- 1 organization (EstateFlow Demo Realty)
- 5 users — admin, 2 sales agents, 1 field executive, 1 social media manager
(default password:
Estate@123!) - 20 sample leads across all sources / statuses
- 10 sample properties with images
- Sample calls, follow-ups, attendance records and social posts
- Default message templates
Login credentials are printed at the end of the seed run.
npm run dev
# open http://localhost:3000Sign in with admin@estateflow.dev / Estate@123!.
| Command | Purpose |
|---|---|
npm run dev |
Start Next.js dev server on :3000 |
npm run build |
Production build |
npm run start |
Serve the production build |
npm run lint |
ESLint |
npm run typecheck |
tsc --noEmit |
npm run seed |
Seed the demo organization, users, data |
- Create a new project, copy the URL + anon + service-role keys.
- Apply the migrations from
supabase/migrations/in order (Supabase Studio → SQL Editor, orsupabase db push). - (Optional) Enable email confirmations under Authentication → Providers → Email.
- Create a public Storage bucket named
property-mediaif you want to upload property photos through the app — image upload uses signed URLs.
- Push this repo to GitHub.
- Import it on https://vercel.com/new.
- Add the env vars from
.env.examplein Project → Settings → Environment Variables. At a minimum:NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEYSUPABASE_SERVICE_ROLE_KEYAPP_PUBLIC_URL(set to the eventual Vercel URL, e.g.https://estateflow-crm.vercel.app)
- Deploy. Subsequent pushes auto-deploy.
After deployment, update the Integrations page inside the app and set
Public app URL to your Vercel domain so property share links and Twilio
webhooks generate the right URLs.
The app supports a fully functioning Twilio Voice bridge between an agent and a lead, with a fallback to the next agent and automatic call logging.
- In the Twilio Console, buy a phone number that supports Voice.
- Open EstateFlow → More → Integrations and fill in:
- Twilio Account SID
- Twilio Auth Token
- Twilio phone number (E.164 format, e.g.
+15551234567) - Twilio voice webhook base URL (your public origin, e.g.
https://estateflow-crm.vercel.app)
- Toggle Dry-run mode off when you're ready to place real calls.
- The Twilio number's Voice & Fax config will hit:
These are created by the app — you don't need to configure them in the Twilio Console as long as your
{APP_PUBLIC_URL}/api/twilio/voice/agent-prompt?callId=<call_id> {APP_PUBLIC_URL}/api/twilio/voice/dial-lead?callId=<call_id> {APP_PUBLIC_URL}/api/twilio/voice/status?callId=<call_id>Twilio voice webhook base URLis reachable.
Use ngrok to tunnel to http://localhost:3000:
ngrok http 3000
# copy the https URL into Integrations → Twilio voice webhook base URLOr simply leave Dry-run mode on — the flow will record everything in the
calls and activities tables but Twilio won't be invoked.
External platforms (36 Acre, MagicBricks, Housing.com, Facebook Lead Ads,
Zapier, Make, your own forms) can POST leads to:
POST {APP_PUBLIC_URL}/api/webhooks/leads
Content-Type: application/json
X-Org: <organization_slug>
X-Signature: sha256=<hex(hmac_sha256(body, lead_webhook_secret))>
The webhook accepts both camelCase and snake_case keys. Example payload:
{
"fullName": "Rahul Sharma",
"phone": "+919999999999",
"email": "rahul@example.com",
"source": "36 Acre",
"propertyType": "Apartment",
"budgetMin": 7500000,
"budgetMax": 12000000,
"preferredLocation": "Gurgaon",
"notes": "Looking for 3BHK near Golf Course Road"
}ORG=estateflow-demo
BODY='{"fullName":"Rahul Sharma","phone":"+919999999999","source":"36 Acre","propertyType":"Apartment","budgetMin":7500000,"budgetMax":12000000,"preferredLocation":"Gurgaon"}'
SECRET=$(psql "$SUPABASE_DB_URL" -tAc "select lead_webhook_secret from integration_settings where organization_id=(select id from organizations where slug='$ORG')")
SIG="sha256=$(printf %s "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $2}')"
curl -X POST "$APP_PUBLIC_URL/api/webhooks/leads" \
-H "Content-Type: application/json" \
-H "X-Org: $ORG" \
-H "X-Signature: $SIG" \
-d "$BODY"On success the webhook:
- Inserts the lead with
sourcenormalized ("36 Acre"→36_acre). - Assigns it to an agent (round-robin / least-busy / manual based on
integration_settings.assignment_mode). - Records an
activityand notifies the agent. - Triggers the bridge-call automation (or simulates it in dry-run mode).
- Import the same URL.
- Use a Pre-request Script to set the signature header:
const body = pm.request.body.raw; const secret = pm.environment.get("WEBHOOK_SECRET"); const sig = CryptoJS.HmacSHA256(body, secret).toString(); pm.request.headers.upsert({ key: "X-Signature", value: `sha256=${sig}` });
| Role | Can do |
|---|---|
| Admin / Owner | Everything: invite users, manage roles, see all data, configure integrations |
| Sales Manager | View / assign leads, manage follow-ups, view performance reports |
| Sales Agent | See assigned leads, call, send property details, add notes & follow-ups |
| Field Executive | Check-in / check-out, view assigned site visits, add visit notes |
| Social Media Manager | Manage social calendar, create / schedule posts |
- Bottom navigation (Dashboard · Leads · Properties · Follow-ups · More).
- Large tap targets, WhatsApp-style quick action buttons.
- Sticky CTA bars on lead and property detail pages.
- One-tap call, WhatsApp follow-up and property share from the lead page.
- GPS check-in works on any mobile browser that supports the Geolocation API (Safari, Chrome, Firefox).
- Sign in / sign up
- Admin can invite users & manage roles
- Manual lead creation
- Webhook lead creation with signature verification
- Round-robin lead assignment
- Bridge-call automation (Twilio + dry-run)
- Call logs persisted with status, duration, recording URL
- Agent-only views of assigned leads
- One-click call / WhatsApp / SMS / email from a lead
- One-click property share with public link
- Property CRUD with multiple photos
- Lead timeline (calls, notes, shares, messages, follow-ups)
- Attendance check-in / check-out with GPS
- Admin attendance dashboard
- Social posts draft / schedule / publish
- Dashboard CRM metrics + reports
- Mobile-first responsive UI
- Cloud-ready Supabase + Vercel
MIT.