Back to all work

B2B SaaS - Vendor Compliance & Document Tracking

COI Vault

A multi-tenant SaaS platform that tracks vendor certificates of insurance, expirations, and compliance - with automated reminders and full audit trails. Built for property managers, condo boards, and general contractors.

Application Screenshot

Replace with actual screenshot

Problem

Property managers and contractors manually track vendor COIs in spreadsheets. They miss expirations, exposing themselves to liability gaps. There's no purpose-built tool for this - just generic document managers that don't understand compliance workflows.

Why It Matters

A single lapsed certificate of insurance can expose a property management company to six-figure liability. This isn't a nice-to-have - it's a compliance requirement that most teams handle with spreadsheets and prayer. COI Vault turns a reactive, error-prone process into a proactive system with automated enforcement.

Architecture Overview

Server-first Next.js application using the App Router pattern. Dashboard pages are Server Components that fetch data directly from Prisma - no client-side fetching, no loading spinners, no waterfall requests. All mutations go through Server Actions with Zod validation and auth checks at every boundary. Multi-tenant isolation is enforced at the data layer: every query filters by organization ID, and every server action verifies org membership before proceeding.

┌──────────────────────────────────────┐

│        Architecture Diagram        │

│     Replace with actual diagram     │

└──────────────────────────────────────┘

Stack Breakdown

Next.js 16 (App Router)

Server Components + Server Actions for zero-waterfall data fetching

TypeScript

End-to-end type safety from database schema to UI

Prisma + PostgreSQL

Type-safe ORM with relational data model for multi-tenant architecture

NextAuth (JWT)

Credentials-based auth with JWT sessions - no session table overhead

Stripe

Checkout, Billing Portal, and Webhooks for subscription lifecycle

Resend

Transactional email for expiry reminders

Vercel + Cron

Edge deployment with scheduled cron jobs for automated reminders

Vitest

Unit and integration testing

Tailwind CSS v4

Utility-first styling with zero runtime overhead

Technical Decisions

01

Server Components for dashboard pages

Dashboard data doesn't need interactivity on initial render. By keeping pages as Server Components, we eliminate client-side fetching entirely - the HTML arrives with data already rendered. This removes loading states, reduces JavaScript bundle size, and makes the app feel instant.

02

Server Actions for all mutations

Every write operation (create vendor, upload document, change plan) goes through a Server Action with Zod validation. This creates a single enforcement layer - auth checks, plan limits, input validation, and audit logging all happen in one place. No API routes to maintain separately.

03

JWT sessions over database sessions

For a B2B SaaS with moderate user counts, JWT sessions eliminate a database query on every request. The tradeoff is that session revocation requires token expiry rather than instant invalidation - acceptable for this use case.

04

Soft deletes with audit trail

In a compliance-focused product, data should never disappear. Every deletion is a soft delete (setting deletedAt), and every action creates an audit log entry. This satisfies B2B compliance requirements and enables full traceability.

05

Plan enforcement at the action layer

Plan limits (max vendors, documents, seats) are checked inside Server Actions - not in the UI. The UI shows limits for UX, but the server enforces them. This prevents any bypass through direct API calls or UI manipulation.

Tradeoffs

Chose NextAuth credentials provider over OAuth for simplicity - limits social login options but reduces third-party dependencies

In-memory rate limiting instead of Redis - sufficient for current scale, would need Redis for horizontal scaling

Stripe webhook verification happens synchronously - keeps the handler simple but adds latency to webhook processing

Single-region deployment on Vercel - acceptable latency for North American users, would need edge functions for global distribution

Scaling Considerations

Database connection pooling via Prisma + Neon's serverless driver for handling concurrent connections

Audit log table will grow unbounded - needs a retention policy or archival strategy at scale

Cron-based reminders work for hundreds of orgs; at thousands, would need a queue-based system (BullMQ, SQS)

Multi-tenant query isolation relies on application-level orgId filtering - Row-Level Security (RLS) would add database-level enforcement

Explore the code

The full source code, documentation, and architecture decisions are available on GitHub.