Skip to content
Back to projects
Final validation · pre-beta · designed end to end

Agentic CRM

A production-grade, AI-assisted, multi-tenant SaaS CRM. It reads the inbox, proposes the next action, and keeps a human firmly in control. The platform is in final validation and deployment testing ahead of a small public beta — core CRM, AI workflow execution, workspace isolation, authentication, billing, and inbox synchronization are operational.

120+ API Routes
Multi-Tenant SaaS
JWT + RBAC + OAuth
Redis Workers
AI Approval Engine
Docker + Kubernetes Ready

Problem

Sales and account teams drown in manual CRM upkeep. Emails arrive, context scatters across threads, and follow-ups slip through the cracks. The CRM — the supposed source of truth — becomes a chore that people update late, partially, or not at all.

I lived this from the business side for years. The tooling never matched how the work actually happened. I wanted a CRM that does the upkeep for you by reading the inbox and proposing the next move — without ever silently acting on your behalf.

Why I built it

Agentic CRM is my attempt to build the kind of business software I wish existed when I was working with clients: intelligent enough to reduce manual work, but safe enough to keep humans in control.

It's also a deliberate engineering exercise. I wanted to prove I could design and ship a real SaaS system — multi-tenancy, auth, billing, background processing, and AI in the loop — not a demo. The constraints are what make it interesting.

Product vision

The inbox becomes the input. AI reads incoming email, understands intent, and drafts SuggestedActions — create this company, add this contact, open this deal, schedule this task. The user reviews a clear approval surface and decides. Approved actions execute as linked CRM records in the correct dependency order.

The result: the CRM stays current because keeping it current is now one click of review, not an afternoon of data entry. The whole flow — from inbox to executed records — is gated by an explicit human approval boundary:

AI approval pipeline
Ingest
Gmail
OAuth source
Sync Worker
background
Email Repository
normalized
Intelligence
AI Analysis
extract intent
SuggestedActions
proposed
Control
Human Review
in the loop
Approval Boundary
nothing passes unapproved
Execution
Execution Engine
resolves dependencies
Linked CRM graph
companies · contacts · deals · tasks · interactions

The AI proposes. A human approves. Only then does anything execute — writing a linked CRM graph (below), not a fixed chain.

Architecture overview

A React + TypeScript frontend talks to a FastAPI backend over a typed REST API. PostgreSQL is the system of record; Redis backs caching and the job queue; background workers handle inbox sync and AI analysis off the request path. Everything is containerized for Docker and orchestrated with Kubernetes.

System architecture
Audit LoggingBrowserReact FrontendTypeScript · ViteAPI GatewayFastAPI · 120+ routesAuth LayerJWT · Sessions · CSRF · RBACWorkspace Isolationtenant boundaryCRM Servicesdomain logicEmail ServicesGmail syncAI Servicesanalysis · actionsRedis Queuejobs · cacheWorkersasyncExecution Engineresolves dependenciesPostgreSQLsystem of record

A distributed request path: auth and tenant isolation up front, domain / email / AI services fanning out, async processing through Redis + workers, with audit logging as a cross-cutting concern.

Frontend architecture

React, TypeScript, Vite, and Tailwind. The frontend is organized around CRM entities and the approval workflow: a focused review surface for SuggestedActions, entity views for companies, contacts, deals, and tasks, and clear state for what AI proposed versus what a human approved.

Type safety runs end to end — API contracts are typed so the UI and backend stay in agreement, which matters when the data model spans linked entities and tenant boundaries.

Backend architecture

FastAPI and Python with SQLAlchemy as the ORM and Alembic for migrations. The API spans 120+ routes organized by domain — auth, workspaces, CRM entities, email, AI actions, billing — with clear boundaries between request handling, business logic, and persistence.

Long-running and untrusted work (inbox sync, AI analysis) is pushed to background workers via a Redis-backed job queue, so the API stays responsive and failures are isolated and retryable.

api/ — domain layout
app/
├── auth/ JWT · sessions · CSRF
├── workspaces/ tenancy · RBAC · members
├── crm/ company · contact · deal · task
├── email/ Gmail OAuth · sync · import
├── ai/ analysis · SuggestedActions
├── billing/ Stripe · plans · webhooks
└── workers/ queue consumers · schedulers

Database & multi-tenancy

The data model is multi-tenant by design. Every tenant-owned row is scoped to a workspace, and every query is constrained by that scope — no record crosses a tenant boundary. RBAC governs what members can do within a workspace.

This is the part you never want to get wrong. Tenant isolation is treated as an invariant enforced at the data-access layer, not a convention left to individual queries.

Multi-tenant isolation
Workspace A
CompaniesContactsDealsTasksMembersRolesPermissions
Workspace B
CompaniesContactsDealsTasksMembersRolesPermissions
Workspace C
CompaniesContactsDealsTasksMembersRolesPermissions
Enforced at the data-access layer — not left to individual queries.

Every query is workspace-scoped. No row ever crosses a tenant boundary.

Gmail OAuth & inbox sync

Users connect Gmail via OAuth. The backend manages the token lifecycle — authorization, refresh, and scoped access — and a background worker syncs email, normalizes it, and stages it for analysis. Sync runs off the request path so the UI never blocks on the inbox. From there, synced email feeds the AI approval pipeline shown above.

AI SuggestedActions

Imported email is analyzed to extract intent and the CRM operations it implies. The AI doesn't mutate anything — it produces SuggestedActions: structured, reviewable proposals like “create company Acme”, “add contact Jane”, “open deal”, “schedule follow-up”.

Each proposal carries enough context for a human to judge it quickly. The model's job is to draft the work; the human's job is to approve it.

Human-in-the-loop approval

Nothing the AI proposes touches the CRM until a person approves it. The approval surface presents proposed actions as a package, the user accepts or rejects, and only then does execution begin. This boundary is the core safety property of the product.

It's a deliberate design choice: autonomy is seductive and usually wrong for business workflows. Keeping a human in the loop is what makes AI trustworthy enough to actually use on real customer data.

Linked execution: a CRM graph

Approved actions rarely stand alone, and the CRM isn't a fixed hierarchy — it's a graph. A contact can exist on its own or belong to a company; a deal can link contacts, companies, or both; a task can link any combination of entities; and interactions can reference anything. Tasks have child tasks, and deals relate to other deals — renewals, expansions, and follow-on opportunities.

So execution isn't a fixed order — it's dependency resolution. The Execution Engine works out what each approved action requires, creates or links entities in the right order, and writes a consistent linked graph so every reference resolves.

CRM relationship graph
ContactpersonCompanyorganizationDealopportunityTaskaction itemInteractionemail · call · notebelongs to · optionallinkslinkslinksoptionalreferenceschild tasksrelated deals
Contacts may exist independentlyDeals link contacts and/or companiesTasks link any combination of entitiesInteractions may reference any CRM entityChild tasks · related deals (renewal · expansion · follow-on)

The Execution Engine resolves dependencies and writes a linked CRM graph — not a fixed hierarchy.

Security model

Authentication uses JWT and server-side sessions, with CSRF protection on state-changing requests. Authorization is enforced through RBAC scoped to each workspace. Gmail access is limited to the scopes the product needs, and tokens are handled as sensitive credentials.

The whole system is designed with security and deployment in mind from the start — tenant isolation, least-privilege access, and a clear boundary between what AI proposes and what actually executes.

Deployment model

Today every service runs in Docker, orchestrated with Docker Compose for local development: web, API, worker, PostgreSQL, and Redis. The target production architecture is a bare-metal host running a k3s Kubernetes cluster behind Cloudflare — traffic enters through an ingress and fans out to horizontally scalable frontend, API, and worker pods, with Redis and PostgreSQL as managed dependencies. Kubernetes is the planned production topology, not yet deployed.

Current development topology
Docker ComposeRunning today
Frontend
API
Redis
Workers
Postgres

Runs today via Docker Compose — the same service topology as production, on a single host.

docker compose up
$ docker compose up
✓ db postgres healthy
✓ cache redis healthy
✓ api fastapi :8000 ready
✓ worker queue consumer ready
✓ web vite :5173 ready
Target production topology
Internet
Cloudflare
Bare metal hostPlanned
K3s cluster
Ingress
Frontend Pods
API Pods
Worker Pods
Redis
Postgres
Bare-metal hostingk3sHorizontal scalingContainerized servicesBackground processing

Planned architecture — a bare-metal host running a K3s cluster. Designed, not yet deployed.

Engineering challenges

The interesting parts of Agentic CRM aren't the features — they're the constraints. These are the problems that took the most design thought, why each one is hard, and the decision I made.

Multi-tenant isolation

Why it's hard

A single row leaking across tenants is catastrophic, and the boundary has to hold across every query, join, and background job — not just the obvious ones.

Design decision

Workspace scope is an enforced invariant at the data-access layer, not a per-query convention. Background jobs carry workspace context so async work never escapes the boundary.

Human-in-the-loop AI safety

Why it's hard

AI output is probabilistic and can be confidently wrong on real customer data. Letting it write directly would be fast and unsafe.

Design decision

An explicit approval boundary separates analysis from execution. The AI only ever produces SuggestedActions; nothing mutates the CRM until a human approves it.

Gmail OAuth lifecycle

Why it's hard

Tokens expire, refresh, and get revoked; scopes must stay minimal; and a failed sync can't silently lose or duplicate email.

Design decision

Token authorization, refresh, and revocation are centralized and scope-limited. Sync runs as idempotent, retryable background jobs so failures recover cleanly.

Linked execution dependencies

Why it's hard

Approved actions form a graph — a task may need a deal, a contact, and a company that don't exist yet — so naive writes break on unresolved references.

Design decision

A dependency-resolution step orders writes and builds the linked CRM graph transactionally, so every reference resolves and partial state never persists.

Background worker architecture

Why it's hard

Long-running, untrusted work (inbox sync, AI analysis) can't block the request path, and one failing job shouldn't take others down.

Design decision

A Redis-backed job queue runs idempotent, retryable consumers. Failures are isolated and observable; the API stays responsive regardless of worker load.

Composed security boundaries

Why it's hard

Auth, CSRF, RBAC, tenant scope, and the AI/execution boundary all have to compose without leaving a gap between them.

Design decision

Layered defenses: JWT/session auth, CSRF on mutations, RBAC within each workspace, least-privilege OAuth, and a hard AI → approval → execution boundary.

Testing & validation

Validation focuses on the parts most likely to break trust: tenant isolation, the auth and CSRF paths, the OAuth token lifecycle, and the approval boundary that guarantees AI never executes without sign-off. Migrations are exercised so the schema can move forward safely.

With core functionality operational, current work is squarely on shipping: deployment, observability, production hardening, and end-to-end testing ahead of the beta. The goal isn't a coverage number — it's confidence that the invariants that make the product safe actually hold.

Lessons learned

Most of “production” is the unglamorous work: migrations, auth edge cases, token refresh, and deployment. The AI is the easy, exciting part; the system around it is what makes it usable.

An explicit approval boundary turned out to be a feature, not a limitation. Constraining the AI made the whole product more trustworthy — and easier to reason about as an engineer.

Known limitations

  • It's in final validation ahead of a small public beta — not yet operating at production scale, and I don't claim users or revenue.
  • AI quality depends on email content; ambiguous threads produce weaker SuggestedActions that lean harder on human review.
  • Gmail is the first inbox integration; Microsoft Outlook is in progress and planned before beta.
  • Some operational concerns (full observability, multi-region, advanced rate limiting) are designed for but still being built out.

Roadmap

  • Complete Microsoft Outlook integration alongside Gmail.
  • Production deployment on Kubernetes (k3s) infrastructure.
  • Public beta rollout.
  • Observability and audit tooling for real deployments.
  • Expanded AI workflow capabilities.
  • Advanced CRM analytics.

Want the deeper walkthrough?

Happy to walk through the architecture, the code, or the decisions behind any part of Agentic CRM.