All blogs
Engineering Teardowns

Building a Multi-Tool CRM with Supabase Vault and Edge Functions: ClientNest Engineering Teardown

How we built a B2B agency CRM that unifies GitHub, YouTube, PayPal, and Webflow into one timeline — with encrypted OAuth tokens and optimistic Kanban UI

Maitreya KulkarniFounder, Nexolve Technologies
9 min read
Multi-Tool CRMSupabase VaultOAuth Token StorageReact Query Optimistic UIB2B SaaS EngineeringMulti-Tenant SaaS

Most CRMs assume "deal" means "sales pipeline opportunity." For digital agencies and product studios, the actual signals are scattered: GitHub commit velocity, YouTube engagement, PayPal payment status, Webflow site uptime. Generic CRMs (HubSpot, Pipedrive) ignore these. ClientNest CRM closes that gap.

This is the engineering teardown of ClientNest CRM — how we securely unified four external APIs into one timeline, the optimistic UI patterns that made the Kanban feel instant, and why Supabase Vault was non-negotiable.

The Problem

A typical digital agency works across:

  • GitHub — code review activity, commit velocity, PR merge rates per client
  • YouTube — video performance for content marketing engagements
  • PayPal — invoice status and payment failures
  • Webflow — site uptime and traffic on managed sites

Each has its own dashboard. Critical signals get buried. A video that's underperforming, a payment that's failing, a PR that's been open 14 days — all of these are deal-affecting events that the team should react to within hours, but typically discovers days later.

ClientNest's brief: a single pane of glass where every external signal lands in one chronological timeline, alongside a deal Kanban that updates on those signals.

The Architecture

A Next.js application with Supabase as the data layer and orchestration runtime:

  • Next.js + TypeScript frontend — Kanban pipeline, unified timeline, GSC analytics, billing dashboards
  • Supabase Postgres — Multi-tenant data: orgs, users, clients, deals, events, tokens
  • Supabase Vault — Encrypted storage for OAuth refresh tokens
  • Supabase Edge Functions — Cron-style jobs that poll connected APIs and write events
  • Supabase Auth — Multi-org auth with role-based permissions
  • PayPal Subscriptions — Server-side billing with webhook-driven entitlement updates

The flow: a user connects their GitHub via OAuth. The token gets encrypted into Supabase Vault. An Edge Function cron runs at the user-configured interval (15 minutes to 24 hours), pulls events using the decrypted token, deduplicates by external ID, and upserts into the events table. The frontend subscribes to that table via Supabase Realtime; new events appear in the timeline within seconds.

Key Technical Decisions

OAuth Tokens in Supabase Vault, Never in Environment Variables

Storing third-party OAuth tokens is one of the most common security mistakes in B2B SaaS. The wrong patterns:

  • Plaintext in Postgres
  • In environment variables (multi-tenant violations)
  • Encrypted with a single app-level key (single key compromise = full breach)

We used Supabase Vault, which encrypts secrets at rest with a separate key per project, with audit logging on every read. Tokens are decrypted only at the moment of use within Edge Functions; they're never exposed to the application layer.

Token refresh is automated: when an Edge Function detects a 401, it triggers the refresh flow, updates the Vault entry, retries the original call. Refresh failures escalate to the user via in-app notification ("Your GitHub connection expired — reconnect here").

For the broader auth context that informed these decisions, see our Authentication for SaaS Startups guide.

Edge Functions Cron with User-Configurable Intervals

Polling APIs at fixed intervals is wasteful. Polling them at user-configurable intervals respects rate limits and lets paying users get faster sync.

We used Supabase's pg_cron extension to schedule per-tenant Edge Function invocations. The scheduling table has columns: tenant_id, integration, interval_minutes, last_run_at. A master cron runs every minute, finds tenants whose last_run_at + interval_minutes has passed, and queues their integration runs.

GitHub's 5K-req/hr rate limit and YouTube's quota system both get respected naturally — slower-tier customers poll less; the master cron throttles based on remaining quota.

Optimistic Kanban with React Query

Kanban drag-and-drop must feel instant. The naive flow — drag, await server response, then update UI — adds 200–500ms of perceived lag and tanks UX.

We used React Query's useMutation with the onMutate optimistic-update pattern:

  1. User drags card from "In Review" to "Approved"
  2. onMutate immediately updates the local cache to show the card in the new column
  3. The actual server call goes out in the background
  4. If it succeeds, nothing visible happens
  5. If it fails, onError rolls back the cache and shows a toast

The result: drag-and-drop feels native. The trade-off: brief inconsistency window (~200ms) where local state is ahead of server state. We accept this; the alternative (laggy UI) is much worse.

Multi-Tenant via Supabase RLS

ClientNest is a B2B SaaS with strict org-level data isolation. Every table has org_id as the tenant key. Postgres Row-Level Security policies enforce that users only see rows where org_id matches their session's claim.

This is non-negotiable. Application-level tenant scoping has too many ways to be forgotten in a single query. RLS at the database level is bulletproof. For the deeper architecture pattern, see our B2B SaaS Multi-Tenant Architecture post.

Built-In GSC Integration as a Differentiator

Most CRMs surface deal signals; almost none surface SEO signals. We integrated Google Search Console at the deal-page level: each client deal can show its impression/click/position data per page, automatically ranked by gap-to-first-page opportunity.

This was a deliberate market positioning choice. Digital agencies care about their clients' search rankings; surfacing this in the CRM made it the obvious tool for the segment.

Why This Matters for SaaS Builders

ClientNest demonstrates a pattern we believe more SaaS products should adopt: don't compete with HubSpot/Salesforce on the standard features; differentiate by integrating signals they don't.

Generic CRMs are mature. The opportunity is in vertical-specific signal integration:

  • Real estate CRMs that surface rental-market data
  • Restaurant CRMs that surface Swiggy/Zomato performance
  • E-commerce CRMs that surface SEO + ad performance + return rates

Each is a CRM market that doesn't exist yet because horizontal CRMs don't bother integrating those signals.

For the broader business context on when custom CRMs win, see Build vs Buy CRM ROI and Salesforce vs Custom CRM.

What We'd Do Differently

  • Use Stripe Checkout instead of PayPal Subscriptions. PayPal's webhook reliability is worse and the dispute flow is more painful.
  • Move event ingestion to a queue (BullMQ or Inngest) instead of Edge Function direct writes. The current pattern is fine at scale but a queue would handle burst load more gracefully.
  • Add full-text search on events from day one. We added it later; users wanted "find all events mentioning X" early in beta.

Where Nexolve Fits

We build vertical SaaS and B2B platforms via our SaaS & Web Apps service. For the full project context, see the ClientNest CRM case study.

Working on something similar?

Nexolve scopes, designs, and ships production software for startups and growing businesses. Tell us what you're building — we come back with a scoped plan within 48 hours.

Related reading