Case study · Staffing tech · AI product engineering

A building on the sidewalk becomes a filed CRM record in seconds.

FlyCRM is a mobile-first, AI-native field CRM for staffing reps who prospect on the road. Photo + GPS in, enriched company profile out — behind a provider-agnostic AI layer, sixteen AI surfaces, and database-enforced multi-tenancy that keeps every rep's book of business cleanly their own.

Client · WurkNowEngagement · End-to-end product engineeringTimeline · Jun 29 → Aug 1, 2026 · ~5 weeksStack · Next.js 16 · Supabase · Vercel AI SDK · Redis
16
AI surfaces across capture, search, pipeline and comms
236
Row-level security policies enforcing tenant isolation
326
Unit, component and end-to-end suites gating CI
0
Records written by a model without a human confirm
01Executive Summary

A CRM that has to earn its first minute in the field

Staffing reps prospect physically. They drive an industrial park, notice a warehouse that is clearly hiring, and then do nothing with it — because logging a lead means twenty minutes of typing nobody does at a stoplight. WurkNow's thesis was sharp: the CRM that wins the field rep is the one that turns a sidewalk observation into a complete, enriched, filed record before the rep gets back in the car.

FlyCRM delivers that as its opening move, then earns the rest of the day: pipeline, tasks, proposals, a unified Gmail and Outlook inbox, reporting. Sixteen AI surfaces remove typing and reading wherever they can, every model call routes through one provider chokepoint that meters spend and enforces entitlements, and the model is kept out of the write path entirely — it proposes, the rep commits.

The core mandate: make field capture feel like magic, make the money and the tenancy boundaries provably correct at the row level, and ship the whole thing — four launch-critical pillars, no cuttable scope — in roughly five weeks.
02The Challenge

Four pillars, all launch-critical, none cuttable

Any one of these is a product on its own. The business model made two of them inseparable from the first migration.

🚗

Reps don't do data entry

The field promise had to work first: photo and GPS in, an enriched, filed company record out. Everything else is table stakes reps would only adopt if the field magic earned their trust.

🧾

A business model that dictates the schema

Reps are free forever and keep their personal CRM across employers. Agencies pay to buy the book of business a rep built for them — with the rep's explicit approval, and only that employer's slice.

🧠

AI everywhere, without sixteen ways to leak

Sixteen AI surfaces mean sixteen chances to overspend, hallucinate into the database or read someone else's data. They needed one enforcement path, not sixteen careful implementations.

⏱️

Five weeks, full scope

Field capture, an AI-native assistant, the free-CRM-plus-workspace ownership model and a CRM core good enough for daily use — sequenced so the riskiest flow was retired first, not last.

The hardest constraint wasn't the AI. It was that a monetization mechanic — “an agency buys the rep's data, the rep approves, nothing leaks sideways” — had to be true at the row level in Postgres before a single rep record was ever collected. Engineering note from the Phase 2 threat model
03The Signature Flow

Photo + GPS → an enriched company profile

Flagged as the project's highest technical risk on day one and built in the third phase to de-risk it early. It is not one AI call — it's a chain of cheap deterministic work that earns the right to spend a token.

One tap in the field

The rep taps the camera in the search bar. The browser grabs coordinates; the frame is downscaled client-side to ~1024px before it ever leaves the phone.

A ranked list, not a guess

Nominatim resolves the address, Overpass returns the businesses actually registered around that point, and candidates are scored deterministically by distance and name signal.

The record writes itself

One grounded, schema-validated model call turns that bounded evidence into a profile. The rep confirms; the company and a GPS-stamped visit are written.

1

Capture

Geolocation plus a canvas downscale on the device.

no tokens
2

Reverse-geocode

Coordinates to a street address via OpenStreetMap's Nominatim — free, no key, no lock-in.

no tokens
3

Discover neighbours

Overpass returns businesses registered at and around that point — the candidate set.

no tokens
4

Rank & enrich

Deterministic distance and name scoring, then firmographics: industry, size, SIC/NAICS, people.

no tokens
5

Extract & confirm

One bounded, schema-validated call turns the evidence into a profile the rep approves.

1 model call
Ranked, never certain

Always a list

The pipeline returns the full ranked candidate list, never a single hard match. The UI leads with the best guess and keeps “No — pick another” one tap away, because a strip mall has six tenants.

Bounded evidence

No raw images to the LLM

Extraction receives a bounded text payload built from geocode, places and firmographic data. Photo bytes only reach a vision model on the separate no-GPS branch, under an enforced server-side size cap.

Merge, don't clobber

Fills gaps only

Saving into an existing company fills missing fields and never silently overwrites something a rep typed by hand. Enrichment writes are restricted to an explicit allow-list of columns.

04The AI Layer

Sixteen surfaces, one enforcement envelope

“AI-native” is easy to claim and hard to ship. The approach: put AI wherever it removes typing or reading, route every single call through one provider chokepoint, and keep the model out of the write path entirely.

resolve providerbranch on BYOdebit creditsreserve tokensone bounded callreconcilereturn

Nearly every AI entry point in the codebase is this same seven-step envelope. New surfaces get written by copying an existing one — so a new feature inherits the metering, the isolation and the failure handling by construction instead of by review.

Conversational search

Ask AI — one bar, four sources

A streaming multi-step agent that decides which tools to reach for: the rep's own CRM (RLS-scoped to their space), Apollo company search, Apollo people search, and native provider web search. “Find warehouses near me hiring forklift drivers who aren't already in my pipeline” resolves across all four.

Every tool input is a zod schema, because model-generated arguments are untrusted input. Workspace and user identity are threaded from the authenticated session — never from anything the model produced.

Agent · 12 tools

The assistant, propose-only

Twelve tools spanning pipeline queries, record briefings, thread lookups, semantic note search and mailbox overviews — plus six proposal tools: move a stage, create a task, log a note, draft a follow-up, add a company, add a contact.

The critical design line: the assistant's tool executors are read-only. They verify ownership, then return a plain proposal object. The rep's explicit Confirm tap calls the real, already-authorized action.

Vision + OCR

Camera as an input method

When GPS is denied or unavailable, the same camera tap routes to a vision identify pass instead. Business cards run through client-side WebAssembly OCR first, escalating to a vision model only when the local parse comes back weak — the cheap path is always tried first.

Semantic recall

Notes that answer questions

Contact notes are embedded on save (1536-dim), so the assistant can answer “what did I promise the Riverside plant manager?” by meaning rather than keyword. With no embedding key configured the feature degrades gracefully — notes still save, search reports itself unavailable.

Inbox

Summarize, draft, smart-sort

Gmail and Outlook sync into one Conversations view. One tap summarizes a thread or drafts an inline reply, both grounded on messages re-fetched server-side under the caller's own permissions — a forged message id resolves to nothing, not to someone else's mail.

Smart Sort classifies a backlog in two phases: a free deterministic triage ladder first, then a capped AI pass over only what the ladder couldn't decide — biased toward false negatives, so an uncertain thread stays visible rather than being hidden.

Extraction

Action items with a citation requirement

Thread action-item extraction must return a verbatim quote for every item it proposes. Server-side, each quote is re-verified as a real substring of the transcript; anything that fails is dropped and the drop count is surfaced to the rep. A hallucinated task can't reach the database.

Deals & documents

Strategy grounded, rates computed

The deal drawer's strategy panel sends only a deal id; everything the model sees is re-read server-side under the caller's own row-level permissions. AI drafts full staffing proposals, but the output schema deliberately has no bill-rate field — bill rates are computed server-side from pay rate and markup.

Templates & connector

Outreach copy, and FlyCRM inside Claude

Generate a template from a plain-language brief, spin variations, retone copy or produce subject-line options — inside the builder the rep already uses. An MCP endpoint with OAuth exposes four role-scoped tools, so an agency exec can ask their own AI assistant for pipeline health and get answers scoped by the same permission model as the app.

05Where AI Is Refused

If a number drives a decision or a dollar, it is computed

The most consequential AI decision in the build was choosing which numbers a model is not allowed to produce. Deal health is one of them: every point comes from an auditable signal, and it ships with a human-readable reasons list.

idle days
stage progress
deal value
close date
open tasks
proposal status
78/100
zero tokens
Reasons attached, every point traceable to a signal.
Deterministic

Stalled detection

“Stalled” is a deterministic rule across four activity signals, not a model opinion — free, instant and explainable. The assistant may paraphrase those reasons; it may never invent the score.

Deterministic

Route planning

“Who's nearby right now?” sorts stalled deals by real distance from the rep's current position using their last known site visit — and the rep's live coordinates are used for the sort and never stored.

The rule we held to: AI is for language, ranking and extraction. Arithmetic, entitlements and money stay deterministic — which also makes the intelligence layer free to run and easier to defend.
06The Trust Model

Guardrails as architecture, not prompt text

Every safety property is structural — enforced by control flow, database policy or type system — so it can't be lost to a refactor or talked around by a clever prompt.

Tenant isolation, twice over

236 row-level security policies across 58 tables, plus explicit workspace scoping in every server action. A forged or foreign record id resolves to zero rows rather than leaking.

enforced by · Postgres RLS + server-action scoping

Bring-your-own key never meters

Provider resolution returns before any central-key read on the BYO branch, so the metering functions are structurally unreachable. A rep on their own key has no entry in the platform's token counter — not even a zeroed one.

enforced by · control flow, not a conditional in the meter

No runaway spend

Atomic reserve-then-reconcile token accounting in Redis: every metered call reserves an estimate plus a bounded worst-case output before the provider call, then trues it down to actual usage after. A near-cap request can't overshoot.

enforced by · atomic Redis reservation, pre-call

Fixed-cost features stay predictable

A separate credit ledger debits a fixed cost before the call and refunds on failure, drawing plan-grant credits before never-expiring purchased packs — every draw recorded in an auditable ledger.

enforced by · append-only ledger, grants drawn first

Feature gating without redeploys

One data-driven entitlement gate reads a boolean per feature from the plans table. Flipping a row in the operator console flips the gate; the client-side lock state is cosmetic and re-checked server-side on every call.

enforced by · server-side re-check on every call

AI quality regression

A grounding and tool-selection eval harness runs the real search orchestration against an adversarial reference set — duplicate-relationship and stale-data cases included. It runs in warn mode until the judges are calibrated against expert labels before being allowed to gate merges.

enforced by · adversarial reference set in CI
Vendor independence, on purpose: one provider-agnostic interface sits behind Anthropic and OpenAI adapters, with the default operator-configurable at runtime and per-user BYO keys held in an encrypted vault. Swapping the platform default is a config change, not a deploy.
07Delivery

Five weeks, phase-gated, verified as it went

Full scope in a five-week window only works if the sequence is right. The build ran as twenty planned phases, each with explicit success criteria, a threat model, and tests that had to be green before the next phase started.

Jun 29 · Phase 1

Foundation

Schema and row-level security, email/Google/Microsoft auth, workspace auto-creation with email-domain routing, three role-scoped app shells, and a Dockerized local stack matching CI. Everything downstream depends on this being correct.

Jul 1 · Phase 2

CRM core and the data-purchase mechanic

Contacts, companies, deals Kanban, tasks, enrichment and all three dashboards — plus the rep-approved scoped data transfer, tested end-to-end before any real rep data existed. Reopened later to close an invite-privilege gap at both the app and database layer.

Jul 2 · Phase 3

AI infrastructure and the signature pipeline

Provider-agnostic layer, pre-call token metering, text/voice/image/conversational search, nearby discovery, map view, and Photo + GPS — the highest-risk flow, retired early.

Jul 2 · Phase 4

Field capture and email sync

Geo check-in, on-site photo/OCR/voice capture filed against records, RLS-enforced storage, and Gmail + Outlook sync with queued workers and automatic watch/subscription renewal.

Jul 3 · Phase 5

Assistant, metering, quotes, reports

The full assistant with propose-then-confirm writes, BYO-key support, overage alerts, the workers-comp-aware quoting module, and reporting plus leaderboards for all three roles.

Jul 6 · Phase 6

Onboarding, settings, MCP, handover

Self-service onboarding with consent and CCPA disclosures, notification center, the MCP connector, cross-device QA including real iOS Safari, and handover docs for client-owned infrastructure.

Jul 7–10 · Phases 7–12

Commercial layer and enterprise hardening

Plans, subscriptions and credit packs with operator-tunable per-action costs; a redesigned marketing site with live pricing; then enterprise record UX, real server-side pagination on the heaviest lists, and multi-tenant workspace switching hardened across all three roles.

Jul 11–29 · Phases 13–19

Design system and product polish

A full design language port onto a tokenized component system with light and dark theming, then surface-by-surface migration: records, pipeline, intelligence, admin, auth and onboarding — with CI gates that fail the build on hardcoded colors or token-scope violations.

In flight

Consolidation, then WhatsApp

Design-system consolidation and the accessibility and contrast audit are closing out. WhatsApp Business messaging is sequenced behind a client-owned Meta Business verification with a one-to-three-week external lead time — flagged as a dependency on day one rather than discovered late.

08Quality Gates

Testing was scope, not a trailing phase

Suites run in CI against a real local Supabase stack — the same start → migrate → test path CI uses — so row-level security behaves identically in development, CI and production.

262
Unit and component suites
64
Playwright end-to-end specs
109
Sequenced, re-runnable migrations
20
Phases, each with its own threat model

Migrations with a numbering discipline

Written to be re-runnable, documented after a real collision taught us that a duplicated migration number is silently skipped rather than loudly rejected.

Design-system CI gates

The build fails on hardcoded hex in application surfaces, which kept theming and dark mode intact as thirty-plus screens were migrated.

Threat models tracked to closure

Per-phase, with named threats — including the two that a passing mocked test suite hid and only live-stack verification caught.

Next.js 16TypeScript · strictSupabase · PostgresRow-level securityVercel AI SDKAnthropic + OpenAI adaptersEmbeddings · 1536-dimMCP connector · OAuthRedis token accountingGmail + Outlook syncNominatim · OverpassApollo firmographicsWebAssembly OCRPlaywright
09Where It Landed

A launch-ready product, and a platform that can absorb more AI

FlyCRM is feature-complete against its launch scope and in pre-launch verification for August 1, 2026. It ships as a mobile-responsive web app — no app-store gatekeeping — deployable to the client's own Vercel and Supabase accounts with architecture and schema documentation at handover.

The field promise, working

Photo + GPS, camera vision fallback, card OCR, voice capture, geo check-in and nearby discovery — the whole capture surface, on a phone browser, at 375px with 44px touch targets.

Highest-risk flow shipped in phase 3 of 20
A CRM reps will actually open

Companies, contacts, deals, tasks, proposals, templates, reports, a unified Gmail and Outlook inbox, and a fully themed design system with light and dark modes.

16 AI surfaces · one enforcement envelope
Monetization with consent built in

Free personal workspaces, plan tiers with operator-tunable credits, and rep-approved scoped data transfer — proven end-to-end before any real data existed.

Partitioned per rep × workspace from migration 1

What we'd carry into any AI product build

Spend the cheap resource first. Deterministic rules, cached lookups and free APIs run before any model call. The entire stalled-deal, deal-health and route-planning layer costs zero tokens — and is more explainable for it.

Let the model propose; make the human commit. Read-only tool executors returning proposals kept a twelve-tool agent completely out of the write path, which is what made it safe to give it real CRM reach.

Require citations, then verify them in code. Demanding a verbatim quote and re-checking it server-side turns “please don't hallucinate” from a hope into a filter.

Make the safety property structural. The BYO-key wall holds because the metering code is unreachable on that branch — not because a reviewer remembered to check.

Front-load the risky flow. The pipeline everyone worried about shipped in week one of five, which is why the last two weeks were polish instead of panic.

The strategic outcome: a rep gets a filed, enriched record from a photo taken at a stoplight, an agency gets data it can buy only with that rep's consent, and the platform can add its next AI surface by copying an envelope that already meters, isolates and fails safely.

Our reps live in their vans. If logging a visit takes more than a few seconds it just doesn't happen, and for years it didn't. Now they take a photo and the record is sitting there waiting for them. Five weeks, the whole thing. I still don't quite believe that part.

Idea in your head? Let’s
bring it to life.

Got a project? A wild idea? Or just want to say hey?
We're here for all of it — reach out anytime.

I’m looking for a help with:

I’m hoping to stay around of (in USD):