Techrise CRM
Multi-tenant B2B CRM handling the full sales lifecycle — pipeline management, bulk email/SMS campaigns, support ticketing, geolocation, and a granular permissions system. Built on Next.js server components with a strict service-layer API over PostgreSQL.
Overview
Techrise CRM is a full-stack multi-tenant CRM built with Next.js App Router, PostgreSQL, and Tailwind CSS. Server components reach the database directly via server actions — there is no separate API server. The frontend, business logic, and data layer are a single Next.js application deployed to Vercel, backed by a Neon serverless PostgreSQL instance.
It handles lead-to-customer pipelines, bulk email and SMS campaigns, support ticketing, and activity logging for B2B sales teams at TechriseGlow Limited.

Architecture
The stack is deliberately three layers with no extra moving parts:
Browser → Next.js Server Component / Server Action → Drizzle ORM → PostgreSQL (Neon)
Pages fetch data inside server components using async/await — no useEffect, no loading spinners, no client-side API calls for initial data. Mutations go through server actions that validate input, enforce permissions, and write to PostgreSQL in a single round-trip.
Tailwind CSS handles all styling. No component library — every UI element is composed from utility classes, which keeps the bundle lean and makes layout changes fast.
Database Design
The schema is built with Drizzle ORM across 30+ domain tables: businessPartnersTable, activitiesTable, campaignTable, ticketsTable, dealsTable, and more. Every table carries a companyId foreign key for tenant isolation.
Drizzle was chosen over Prisma for two reasons: TypeScript inference flows directly from the schema without a code-generation step, and it makes raw SQL easy to drop into when query complexity demands it. Complex dashboard aggregations — revenue trends, lead-stage distributions, ticket counts — use Drizzle's query builder with typed joins rather than pulling data into JavaScript to process.
Multi-tenancy is enforced in the service layer, not in queries. Every service function receives the authenticated company context and rejects requests that reference records outside that tenant's scope.
Server Actions as the API Layer
All data mutations are server actions — Next.js functions marked 'use server' that run exclusively on the server. Each action calls a service function that:
- Validates the input with Zod
- Checks the caller's permissions against a granular matrix (
campaigns:create,tickets:manage, etc.) - Writes to PostgreSQL via Drizzle
- Returns a typed
{ success, data?, error? }result
Client components call these directly as async functions. There are no REST endpoints, no fetch calls, no API routes to maintain. The type safety extends from the database schema through the service layer to the component that triggered the action.
Session Management
User sessions are stored server-side with a 24-hour expiry. Session validation happens in middleware before any server component renders — unauthenticated requests never reach application code. Role and permission data is embedded in the session so permission checks in server actions don't require an extra database round-trip.

Key Features
- Pipeline management — lead stages from New through Qualified, deal tracking, revenue forecasting with Recharts visualisations
- Campaign system — bulk email (Nodemailer + Resend) and SMS campaigns to segmented business partner lists, with per-batch tracking and failure logging
- Activity logging — every interaction timestamped and linked to the relevant contact, deal, or ticket; full audit trail
- Support ticketing — ticket lifecycle from creation to resolution with collaborator assignment
- Bulk import — onboard existing customer lists via XLSX upload
- Geolocation — business partners plotted on Leaflet maps for territory management
- Role-based access — permission matrix enforced at the service layer, not in UI


Tradeoffs
Server components make initial page loads fast but complicate patterns that need optimistic UI. Anything requiring instant feedback — drag-and-drop reordering, inline editing — needs a client component boundary and either optimistic state or a local mutation cache. For a CRM that is mostly form-based CRUD, this boundary rarely comes up.
The email template system renders TSX components to HTML strings via Nodemailer. Type-safe and reusable, but the rendering step is non-obvious and was a source of confusion for early contributors.
Retrospective
The server action pattern removed an entire category of bugs — mismatched API contracts, forgotten loading states, stale client-side caches. The tradeoff is that the mental model is unfamiliar to developers used to REST. Clear naming conventions (createLead, updateCampaignStatus) and consistent return shapes made onboarding faster.
The next meaningful upgrade would be adding a Redis caching layer in front of the most-queried dashboard aggregations. Those queries run on every page load for every user in a tenant, and they're the first thing that will feel slow at scale.