All work
Web · 2024

Edge360

Edge360 is lite CRM software. It is a tool to use by businesses to find sales leads and to keep track of their sales prospects.

Edge360
Year2024
  • PERN (React js
  • Node js
  • Express js and Postgres)
— 01

How I framed it.

Edge360 is a multi-tenant CRM and sales-network platform built for B2B service organizations (consulting, finance, agencies) that consolidates 4–6 disconnected tools (spreadsheets, email, Slack, separate CRM/billing/support) into a single workspace. The platform pairs a React 18 SPA with a Node.js/Express + PostgreSQL backend, layering Socket.IO realtime chat, M-Pesa STK Push billing for the East African market, a public tender marketplace, and a sales-agent reseller channel on top of a configurable lead pipeline. Multi-tenancy is enforced at the application layer through organization_id scoping, keeping infrastructure simple while serving every tenant from a single Postgres database.

— 02

What it does.

  • Configurable lead pipeline with custom milestone stages, per-prospect notes with happiness ratings, scheduled tasks, drop/won outcomes, and full transfer history between owners
  • Separate Relationships module for long-horizon contacts (referrers, partners, ex-clients) with social-media JSON and qualitative narrative fields like what_i_know and why_they_important
  • Public Notice Board / Tender marketplace where users post and browse opportunities with attachments, finder fees, commission terms, and admin moderation
  • Real-time per-lead and per-relationship chat threads plus direct messages between team members, delivered live over Socket.IO
  • M-Pesa STK Push subscription billing for the Kenyan / East African market alongside the standard credit-card flow
  • Sales-agent reseller channel with subscription attribution, commission tracking, and dedicated agent pricing tiers
— 03

Inside the build.

Multi-Tenant Application Isolation

Every prospect, relationship, message, target, and support ticket is scoped by organization_id. A single Postgres database serves every tenant, with isolation enforced through JWT-resolved user filters at the controller layer — inexpensive infrastructure with strict logical separation, no per-tenant schemas or row-level security required.

Realtime Chat with Room Targeting

Socket.IO is mounted on the same Express HTTP server. addUser registers a (userId, socketId, prospectId | relationshipId) tuple. sendMessage dispatches either to all users in the matching prospect/relationship room minus the sender (broadcast) or to a specific receiver_id (DM). Presence is held in-memory — fine for single-instance, would need a Redis adapter for horizontal scaling.

M-Pesa STK Push Pipeline

Server fetches a Daraja OAuth token, builds the base64 STK password (Shortcode + Passkey + Timestamp), POSTs to mpesa/stkpush/v1/processrequest, and inserts an mpesa_transactions row in 'Waiting User Action' state. Daraja later invokes the callback URL — the server flips the user to premium, updates users_subscriptions, and persists the raw callback JSON to disk for replay. The client polls a status endpoint until terminal state.

Encrypted-Transit Authentication

Clients AES-encrypt the password before transit using a shared CRYPTO_JS_KEY; the server decrypts then bcrypt-hashes for storage. JWTs are issued in the auth-token header, stored in localStorage, and validated by the fetchuser middleware. A per-user user_logged_in flag enforces a single active session (admin exempt) to prevent silent account sharing.

Per-Tenant Pipeline Configuration

Each organization stores its own milestones[] (default 5 stages, editable), happiness_rating_choices[] (5 emotional descriptors), lead sources, services, industries, and currency as Postgres text/jsonb arrays. Tenants customize the entire CRM vocabulary without schema migrations or backend deployments.

— 04

Under the hood.

  • Multi-tenant isolation enforced at the controller layer via organization_id scoping — single Postgres database serves every tenant without per-tenant schemas or row-level security
  • Stateless JWT auth with per-user single-active-session lock (user_logged_in flag) preventing parallel logins from a second browser
  • AES-encrypted passwords in transit (CRYPTO_JS_KEY) layered on top of TLS, bcrypt-hashed at rest — protects against plaintext leaks in client-side console logs
  • Socket.IO server attached to the same Express HTTP server, keyed off (userId, prospectId | relationshipId) tuples for room-scoped chat
  • M-Pesa Daraja integration: OAuth token → STK push → callback persistence → client polling, with raw callback JSON written to mpesa_callbacks/ for forensic replay
  • Role-based menu rendering (admin / employee / agent + super-admin gate by email) driving the entire dashboard layout from a single CRM container
— 05

The toolkit.

Lead Pipeline

Configurable milestones, notes with happiness rating, scheduled tasks, drop/won outcomes, and full owner-transfer history

Relationships

Parallel module for long-horizon contacts with social JSON and qualitative narrative fields (what_i_know, why_they_important, what_they_value)

Notice Board

Public tender / opportunity marketplace with attachments, finder fees, commission terms, and admin authorization gate

Realtime Chat

Socket.IO per-lead and per-relationship threads plus DMs with in-memory presence tracking

M-Pesa Billing

Daraja STK Push integration for the East African market with raw callback JSON persisted to disk for forensic replay

Agent Channel

Reseller users with subscription attribution via agent_id FK and dedicated agent-tier pricing

— 06

What I learned.

  • Layered application-level multi-tenant isolation (organization_id scoping) over a single Postgres database to ship a working multi-tenant CRM without per-tenant schemas or row-level security overhead
  • Modeled prospects and relationships as two distinct entities because most CRMs incorrectly conflate active sales opportunities with long-horizon network contacts
  • Implemented M-Pesa STK Push end-to-end (OAuth → push → callback → client polling) to serve a market underserved by Stripe-centric SaaS billing
  • Persisted every Daraja callback as raw JSON to disk before processing — invaluable for debugging payment edge cases and reconciling disputed transactions
  • Layered AES-in-transit on top of TLS for password fields specifically to prevent plaintext leaks via client-side console logs and shoulder-surfed devtools
  • Made the pipeline vocabulary (milestones, happiness ratings, lead sources, services, currency) editable per organization via Postgres text/jsonb arrays — tenant customization without schema migrations

Want something like this built?

Start a conversation