One Real Estate CRM for the Desk and the Site Visit
A subscription-based property and lead platform built end to end — a Next.js dashboard for the office, a Flutter app for agents in the field, and Razorpay billing tying the commercial side together.

16 Modules
One connected product
Web + iOS + Android
Shared data model
Razorpay
Subscriptions, not invoices
A Growing Brokerage That Had Outgrown Its Spreadsheets
Our client runs a property consultancy in India - residential resale, commercial leasing, and raw land, handled by a team that spends most of the working day away from a desk. When they came to us, the business was being run out of three spreadsheets, a shared Drive folder of property photos, and roughly a dozen WhatsApp groups. That combination had worked when there were four agents. At twenty-two, it had stopped working in a way that was costing them deals.
The specific breaking point was attribution. Two agents would unknowingly work the same inbound enquiry, call the same buyer within an hour of each other, and the client would find out only when the buyer complained. Property details lived wherever the agent who listed them had put them, so quoting an area or a price meant phoning a colleague. Nobody could answer a simple question like "how many active commercial listings do we have under two crore" without an afternoon of manual counting.
They asked us for a CRM. What they actually needed was two products that behave like one. Akoode built the entire thing: the PostgreSQL data model, the Node.js API, a Next.js dashboard for managers and back-office staff, a Flutter application for agents on site visits, real-time notification delivery over WebSocket, and a Razorpay-backed subscription layer so the platform could be sold on to other brokerages later. We ran it from discovery through to App Store and Play Store submission.
16
Feature Modules
Leads, properties, owners, groups, calendar, maintenance and more, built as one system rather than bolted together.
4
Property Categories
Residential, commercial, plot and land each carry their own field set and validation rules.
3
Platforms Shipped
Web dashboard, iOS and Android from a single API and one shared contract.
0
Duplicate Enquiries
Server-side lead ownership means one agent owns a buyer, and everyone can see who.
Project Info
Client
Confidential, India
Industry
Real Estate / PropTech
Use Case
Lead and property management with subscription billing
Solution
Flutter app, Next.js dashboard, Node.js API, PostgreSQL
Engagement
Fixed Cost
The Data Existed. Nobody Could Reach It in Time.
None of these problems were dramatic on their own. A duplicated call here, a stale price there. What made them expensive was how they fed each other — because listings lived in one place and enquiries in another, no agent could verify anything while standing in front of a client, so they guessed, and every guess created a correction somewhere downstream.
Two Agents, One Buyer
Inbound enquiries landed in a shared inbox with no ownership rules. The same buyer regularly got called twice in a morning by colleagues who had no idea the other had already made contact.
Listings Nobody Could Trust
Property specs were split across spreadsheets, chat threads and photo folders. Agents quoting carpet area or asking price from memory were wrong often enough that the office had stopped trusting the sheet entirely.
Nothing Worked on Site
Every useful record was desktop-bound. An agent at a property who wanted to log a visit or pull a comparable had to call the office and wait, or write it on paper and re-enter it that evening - if they remembered.
Reporting Meant Counting by Hand
Basic pipeline questions took hours to answer because there was no single source to query. By the time a report existed it described a week that had already ended.
They didn't have a data problem. They had twenty-two people making confident decisions from information none of them could actually verify.
Replace the Whole Workflow, Not Patch It
We were explicit with the client early on: layering software over the existing spreadsheet habits would just add a fourth place to look. The brief we agreed on was replacement. Everything an agent touches during a working day had to live in the new system by launch, or the old habits would survive alongside it and nothing would change.
One Record Per Property, Per Buyer
Establish server-owned records with explicit assignment, so a lead has exactly one accountable agent and a listing has exactly one authoritative version. Every downstream feature had to read from that, never from a local copy.
Full Capability in the Field
The mobile app needed to be a peer of the dashboard, not a viewer. Agents had to create listings with photos, log visits, capture notes by voice and update deal stages from a phone at the property.
Model Property Types Honestly
A shop, a two-bedroom flat and an agricultural plot share almost no attributes. Rather than force one flat form, we needed category-specific field sets that stay correct even when someone reclassifies a record later.
Make It Sellable as a Product
The client intended to license the platform to other brokerages. That meant plans, usage limits, billing cycles, coupons, proration on upgrades and self-serve subscription management from day one.
Ship to Both Stores Cleanly
The mobile app had to satisfy Apple and Google review on first submission, which shaped real architectural decisions - particularly around how subscriptions could and could not be sold inside the app.
Built as One System With Two Front Doors
We treated the dashboard and the app as two views onto the same data rather than two projects that talk to each other. One PostgreSQL schema, one Node.js API, one contract. Where the platforms differ, they differ deliberately - the field app is optimised for one-handed use on a site visit, the dashboard for volume work at a desk - but neither owns state the other can't see.
Schema and API First
We modelled properties, leads, owners and subscriptions in PostgreSQL before designing a single screen, with per-category detail tables hanging off a shared property record. The Node.js API was built against that schema and frozen as a contract early, which let the web and mobile tracks run in parallel without drifting.
The Web Dashboard
Next.js and React for managers, back-office staff and administrators – bulk property entry, pipeline oversight, agent assignment, reporting and the full subscription and billing surface. This is where Razorpay checkout lives and where an account owner manages their plan.
The Flutter Field App
A feature-first Flutter codebase of roughly 108,000 lines across 439 files, organised into sixteen self-contained modules. Dio handles networking with a refresh-token interceptor; a shared pagination session drives every list; state stays deliberately close to the widgets that own it.
Real Time and Field Tooling
WebSocket delivery for in-app notifications with Firebase Cloud Messaging for push when the app is closed. On top of that came the things agents actually asked for: voice-dictated notes with on-device transcription, Google Places address lookup, and photo and video capture attached directly to a listing.
Hardening and Store Submission
Before release we moved auth tokens into the iOS Keychain and Android Keystore with a silent migration for existing sessions, hard-disabled verbose logging in release builds, locked App Transport Security to HTTPS, and added guards to the build scripts that refuse to produce a store binary from non-production configuration.
What Agents and Managers Actually Use Every Day
Highlight 01
Lead Pipeline With Real Ownership
Every enquiry gets an owner the moment it enters the system, and that assignment is enforced by the API rather than by convention. Managers reassign from the dashboard; agents see their own book on the phone.
- Server-enforced single ownership per lead
- Stage transitions with full activity history
- Field-level collaboration rules that match the web exactly

Highlight 02
Property Records That Match Reality
Four property categories each carry their own attribute set, validation and area semantics, so a plot listing never asks for a bedroom count. Photos, floor plans and walkthrough video attach to the record itself.
- Residential, commercial, plot and land field sets
- Labelled photo groups with video support
- Area handling that stays correct per category

Highlight 03
Voice Notes After a Site Visit
Agents record a note while walking back to the car and it lands on the record as searchable text. On iOS we wired a native Speech framework bridge; Android uses Google Cloud Speech-to-Text.
- Record, playback and transcribe in one flow
- Native iOS transcription via a custom platform channel
- Text attaches straight to the lead or property

Highlight 04
Notifications That Arrive While It Still Matters
A live WebSocket connection updates the in-app notification centre without a refresh, and Firebase push covers the app being backgrounded or closed. Assignment changes and visit reminders reach the right agent in seconds.
- WebSocket stream for in-session updates
- FCM push with a dedicated high-importance channel
- Deep links straight to the record in question

Highlight 05
Subscriptions, Usage and Invoices
Plans, billing cycles, coupon validation, proration previews and usage metering all run through the platform. Razorpay handles collection on the web; invoices generate as shareable PDFs on the client.
- Razorpay checkout with signature verification and webhooks
- OTP-confirmed plan changes with proration preview
- Client-side invoice PDFs with native share

Chosen for a Product That Has to Stay in Sync
The deciding factor throughout was consistency. Two clients reading one API means the schema has to be the contract, not a suggestion - so we picked a relational store with real constraints, a single Node.js service in front of it, and Flutter for mobile so iOS and Android could not drift apart in behaviour. Real-time sits on WebSocket where the session is live and Firebase, where it isn't, because those are genuinely different delivery problems.
The Interesting Work Was in the Seams
Individually, a form and a billing page are solved problems. The real work was staying true across two clients - a schema surviving reclassification, billing behaving differently on iOS, an auth flow surviving a restart.

Selling Subscriptions Without Breaking Apple's Rules
purchasesThe commercial model is a Razorpay subscription. Apple doesn't permit a third-party gateway for digital access in an iOS app, and in-app purchase would have split billing across two systems.
Our Approach
Razorpay checkout stayed on the web dashboard, with one platform gate suppressing every purchase path in the app behind a flag. A background poller reconciles state after Razorpay's webhook lands.
Reclassifying a Property Without Stranding Data
Each property category writes to its own detail table. Changing a record's category after creation left the old rows orphaned - still attached, still returned by some queries, quietly wrong.
Our Approach
Every read path now keys attribute resolution off the record's current category, not whichever detail row it finds first. The category picker locks once a record exists, backed by regression tests.
A Two-Stage Signup That Survives a Restart
Registration verifies a phone by OTP, then conditionally an email, with the token issued only at the end. An app killed mid-flow left users verified enough to be rejected, not enough to proceed.
Our Approach
Signup was rebuilt as a state machine that persists its stage, honours the backend's verification flag, and rehydrates on launch into exactly the step the user left, through to plan selection.
Tokens in the Keychain, Without Signing Everyone Out
Session and refresh tokens sat in plain preferences storage, readable from a device backup or a rooted handset. A naive migration would have logged out every existing user.
Our Approach
Reads check encrypted storage first and fall back to the legacy location, migrating the value and deleting the plaintext copy on first access, alongside disabled verbose logging.
From Three Spreadsheets to One System of Record
rollout,The clearest measure of success was behavioural rather than numerical. Within the first weeks after rollout the shared spreadsheets stopped being edited — not because anyone banned them, but because the app answered the same questions faster and agents stopped opening them.
Contested Enquiries
No ownership rules meant the same buyer was routinely contacted by two agents on the same day.
Listings Nobody Trusted
Property specs lived in spreadsheets, chat threads and photo folders that disagreed with each other.
Desk-Bound Records
Anything useful required being at a computer, so site visits generated paper that got re-entered later or lost.
Reports That Arrived Late
Pipeline questions took hours of manual counting and described a week that had already finished.
Server-Owned Assignment
The API enforces one accountable agent per lead, visible to everyone, changeable only by a manager.
One Property Schema
Category-aware records in PostgreSQL with photos, video and documents attached to the listing itself.
A Field App With Full Rights
Flutter app that creates and edits records on site, including voice-captured notes that transcribe to text.
Live Pipeline State
WebSocket updates and Firebase push keep every device current without anyone refreshing anything.
Clear Accountability
Every buyer has one named agent, and the duplicate-contact complaints that prompted the project stopped.
Answers in Seconds
Filtering active commercial listings under a price ceiling is now a search, not an afternoon.
Work Logged Where It Happens
Visits and notes are captured at the property, so nothing depends on an agent's memory that evening.
A Product, Not Just a Tool
Plans, metering and Razorpay billing mean the platform can now be licensed to other brokerages.
16Modules Delivered
Leads, properties, owners, groups, calendar, maintenance, notifications and billing shipped as one connected product.
3Platforms Live
Web dashboard plus iOS and Android, all reading one API and one schema.
ZeroPlaintext Credentials
Tokens moved to Keychain and Keystore with a silent migration, and release logging hard-disabled.
Where This Architecture Transfers
Strip out the property vocabulary and what remains is a pattern we've now built and hardened: field staff and office staff working the same records from different devices, with subscription billing and real-time state on top. That shape recurs across a lot of industries.
Property Management Companies
Tenancies, maintenance tickets and inspection visits follow the same field-plus-office split, with recurring rent instead of subscriptions.
Interior and Fit-Out Studios
Site measurements, photo documentation and client approvals captured on location and reconciled against a central project record.
Field Sales Organisations
Any team where enquiry ownership matters and reps work away from a desk gets the same benefit from server-enforced assignment.
Equipment Rental and Leasing
Catalogue records with category-specific attributes, availability, contracts and recurring billing map almost directly onto this model.
Inspection and Survey Services
Scheduled visits, on-site capture with photos and voice notes, then a generated report shared from the device.
Multi-Branch Service Networks
Franchise or branch operations needing per-location data separation, central oversight and plan-based access tiers.
We Build the Whole Thing, Then We Ship It
Plenty of teams can build a screen from a design file. The value we added here was in the parts nobody specifies up front — the schema decision that prevents a data bug eighteen months out, the platform constraint spotted before it becomes a store rejection, the migration that changes how tokens are stored without a single user noticing.
One Team, Every Layer
Database, API, web dashboard and mobile app were designed together by the same team, which is why the two clients don't disagree about anything. Nobody had to reverse-engineer someone else's contract.
Security Handled Before Launch, Not After
We audited the build ahead of submission and fixed what we found — credentials out of plain storage, verbose logging disabled in release, transport security locked to HTTPS. Store guards now block a release binary built from development configuration.
We Know What Stores Reject
The payment architecture was shaped by App Store policy from the first design conversation rather than discovered during review. That decision saved the client an entire submission cycle.
We Chase the Bug to the Schema
When reclassifying a property left stale rows behind, we traced it to the data model instead of patching the display. The fix came with regression tests covering the exact case we found in QA.