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.
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:
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.
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.
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.
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.
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.
Runs today via Docker Compose — the same service topology as production, on a single host.
$ docker compose up✓ db postgres healthy✓ cache redis healthy✓ api fastapi :8000 ready✓ worker queue consumer ready✓ web vite :5173 ready
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
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.
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
AI output is probabilistic and can be confidently wrong on real customer data. Letting it write directly would be fast and unsafe.
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
Tokens expire, refresh, and get revoked; scopes must stay minimal; and a failed sync can't silently lose or duplicate email.
Token authorization, refresh, and revocation are centralized and scope-limited. Sync runs as idempotent, retryable background jobs so failures recover cleanly.
Linked execution dependencies
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.
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
Long-running, untrusted work (inbox sync, AI analysis) can't block the request path, and one failing job shouldn't take others down.
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
Auth, CSRF, RBAC, tenant scope, and the AI/execution boundary all have to compose without leaving a gap between them.
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.