The open source agentic CRM, ported to run entirely on Convex.
One deployment is the database, the agent runtime, the work queue, the cron scheduler, the file store, and the web host.
Live demo · Quick start · Configuration · Email · Deploying · Forking · Compare
This is a port of trycompai/crm that replaces the entire infrastructure with a single Convex deployment. The product carried over intact: durable research agents that enrich companies and contacts, an evidence ledger where nothing about a person is guessed, rechecks that require a stated reason, agents that build agents, and record chat that reads your own history and shows its working.
What changed is everything underneath. No Vercel, no Postgres, no Prisma, no Redis, no separate API server, no Better Auth. The frontend is a Vite React app served by the Convex static hosting component from the same deployment that runs the backend.
Try it at convex.link/crmonconvex (served from good-dog-8.convex.site). The demo runs in demo mode: everything works in real time, content resets every 10 minutes with a Convex cron job, and auth and email are intentionally not configured. The site has a full setup and usage guide at /docs, written for people who have never deployed a backend.
| Layer | Technology |
|---|---|
| Backend | Convex functions, TypeScript end to end |
| Database | Convex database with typed schema and indexes |
| Frontend | React 19, Vite, React Router, Tailwind CSS 4 (dark and light themes) |
| Hosting | @convex-dev/static-hosting serving the built app |
| Agent runtime | @convex-dev/agent with tools, threads, and message history |
| Work queues | @convex-dev/workpool, three pools so slow work cannot starve the dispatcher |
| Brand enrichment | @context-dot-dev/convex, the same Context.dev data the upstream uses; the same key also backs web search and scraping |
| Web scraping | @firecrawl/firecrawl-convex or Context.dev, the chat agent reads pages as markdown with either key |
| Web search | @exalabs/convex-exa or Context.dev, search as an agent tool with either key |
| AI providers | OpenAI, Claude (Anthropic), OpenRouter, DeepSeek, or Grok (xAI) via the AI SDK, switchable in Settings, no key ships by default |
@convex-dev/resend or @agentmail/convex, switchable in Settings |
|
| Caching | @convex-dev/action-cache, 7 day TTL on brand lookups, replaces Redis |
| Rate limiting | @convex-dev/rate-limiter on the enrichment budget |
| Rollups | @convex-dev/aggregate for pipeline value by stage and owner |
| Scheduling | Convex cron jobs plus @convex-dev/crons |
| Durability | @convex-dev/workflow and @convex-dev/action-retrier |
| Migrations | @convex-dev/migrations |
Package manager is npm. There is no monorepo; convex/ is the backend, src/ is the frontend.
git clone https://github.com/waynesutton/trycrm-convex.git
cd trycrm-convex
npm install
npx convex devThe first npx convex dev walks you through creating a free Convex project. It will ask for three required environment variables before the first push:
npx convex env set CONTEXT_DEV_API_KEY unset
npx convex env set FIRECRAWL_API_KEY unset
npx convex env set EXA_API_KEY unsetThe literal string unset is the documented sentinel for running without vendor keys. Each feature reports that it is not configured instead of failing. Put real keys there whenever you have them; the features switch on immediately, no redeploy needed.
Then, in a second terminal:
npm run dev:frontendOpen the printed localhost URL. The app seeds itself with demo data on first visit. Or run both together:
npm run devPrefer letting a coding agent do this? The landing page has a "Copy the setup prompt" button that hands the exact instructions to Cursor, Codex, Claude Code, or whatever you use.
Every outside key is optional in practice. The app degrades honestly: features that need a key say so instead of pretending.
| Variable | What it enables | Without it |
|---|---|---|
CONTEXT_DEV_API_KEY |
Company enrichment from Context.dev brand data, plus web search and page reading for the chat agent when the Exa or Firecrawl key is missing | Enrichment tasks complete with a "not configured" note. Set to unset to run keyless. |
FIRECRAWL_API_KEY |
Chat agent reads web pages via Firecrawl. Context.dev covers this when only its key is set | The tool tells the agent which keys enable it. Set to unset to run keyless. |
EXA_API_KEY |
Chat agent searches the web via Exa. Context.dev covers this when only its key is set | Same honest degradation. Set to unset to run keyless. |
OPENAI_API_KEY |
Chat and agent reasoning when OpenAI is the selected provider | Chat replies name the missing key |
ANTHROPIC_API_KEY |
Chat and agent reasoning when Claude is the selected provider | Same |
OPENROUTER_API_KEY |
Chat and agent reasoning when OpenRouter is the selected provider | Same |
DEEPSEEK_API_KEY |
Chat and agent reasoning when DeepSeek is the selected provider | Same |
XAI_API_KEY |
Chat and agent reasoning when Grok (xAI) is the selected provider | Same |
RESEND_API_KEY |
Outbound email through the Resend component | Email sends are logged as no-ops |
AGENTMAIL_API_KEY + AGENTMAIL_INBOX_ID |
Outbound email plus a persistent agent inbox through AgentMail | Same, logged as no-ops |
SLACK_WEBHOOK_URL |
Slack notifications in simple mode: posts to one fixed channel through an incoming webhook | Slack sends are logged as no-ops |
SLACK_BOT_TOKEN |
Slack notifications in full mode: the channel picker in Settings and the /crm bot |
Same, logged as no-ops |
SLACK_SIGNING_SECRET |
Verifies inbound Slack slash commands for the /crm bot |
Bot routes answer 503; notifications unaffected |
APP_URL |
Overrides the base URL in Slack deep links, for custom domains | Links use the .convex.site URL |
FIRECRAWL_WEBHOOK_SECRET |
Verifies Firecrawl crawl webhooks | Optional; only needed for webhook-mode crawls |
AGENTMAIL_WEBHOOK_SECRET |
Verifies inbound AgentMail webhooks | Optional; unverified deliveries are rejected |
Set any of them with:
npx convex env set OPENAI_API_KEY sk-...None of the five AI keys ship by default. A fresh fork has no model keys at all; the Ask page and record chat answer with the exact key they need instead of erroring. Pick which provider the chat uses in Settings. OpenAI is the default.
Demo mode is a flag on the workspace row, set by the seed. While it is on, writes are open, sign-in is disabled, and the reset cron wipes and reseeds all tables every 10 minutes. The banner in the app counts down to the next reset. Forking this for real use? Turn it off first: see Turning off the demo reset.
Notifications route through one of two components, and Settings has a toggle to pick which:
- Resend is plain outbound email. One key:
RESEND_API_KEY. - AgentMail sends too, and also gives agents a persistent inbox: threads, labels, and delivery status sync into Convex tables reactively. Two values:
AGENTMAIL_API_KEYandAGENTMAIL_INBOX_ID. For inbound mail, registerhttps://YOUR-DEPLOYMENT.convex.site/agentmail/webhookin the AgentMail dashboard and setAGENTMAIL_WEBHOOK_SECRET.
Both can hold keys at the same time; the toggle decides which one sends. With neither configured, sends are logged instead of failing, which is how the public demo runs.
Untested. Built against Slack's current API docs but not yet run against a live Slack workspace. The Activity page logs every send, skip, and failure; start there if something misbehaves, and open an issue if you hit a bug.
Off by default. Turn it on in Settings, Slack, then connect one of two modes. Dev and production keep separate env vars, so run each npx convex env set command twice if you deployed: once for dev, once with --prod.
- Webhook mode posts CRM events to one fixed channel. Create a Slack app at api.slack.com/apps, enable Incoming Webhooks, pick a channel, and set
SLACK_WEBHOOK_URL. Two minutes, no scopes. - Bot mode lets you search and pick the channel from Settings and enables the
/crmslash command. Add thechat:write,channels:read,users:read, andusers:read.emailbot scopes (plusgroups:readfor private channels), install the app, and setSLACK_BOT_TOKEN. Invite the bot to the channel with/invite @your-bot-name.
What posts, each behind its own toggle: new companies and contacts, new deals and stage changes, task completions, and agent run summaries. Every message includes an Open in CRM link. Deliveries retry with backoff through the action retrier component; demo mode never posts, and every send or skip shows on the Activity page.
The /crm bot needs two more things: SLACK_SIGNING_SECRET (from your Slack app's Basic Information page) and a slash command pointed at https://YOUR-DEPLOYMENT.convex.site/webhooks/slack/commands. Then flip the bot switch in Settings, Slack. Commands: /crm find, /crm deal <name> <stage>, /crm note, /crm task, /crm activity, /crm help. Every request is verified with Slack's signed secrets scheme, and only workspace members (matched by Slack profile email against the Team list, or an allowed domain) can act. The full walkthrough lives on the /docs page of your deployment.
The repo uses the Convex ESLint plugin with type aware linting. It enforces argument validators, explicit table names in db.get, db.patch, db.replace, and db.delete, warns on .filter() in queries, and keeps cron jobs off the top of the hour.
npm run lintWrite access runs through one wrapper built on convex-helpers: writeMutation in convex/model/functions.ts calls the access check before every mutation handler. That is the Convex pattern for row level security. When you wire real auth, convex/model/access.ts is the only file that changes.
One deployment serves the backend and the site. Before the first production deploy, set the required env vars on production. They are separate from dev, so the values you set during setup do not carry over, and the push fails with MissingEnvironmentVariables without them:
npx convex env set CONTEXT_DEV_API_KEY unset --prod
npx convex env set FIRECRAWL_API_KEY unset --prod
npx convex env set EXA_API_KEY unset --prodThen, from a configured project:
npm run deployThat runs npx @convex-dev/static-hosting deploy, which does the whole thing in one shot: builds the frontend with the production Convex URL, deploys the Convex backend, and uploads the built files to Convex storage.
Your site is then live at your deployment's .convex.site URL, which the Convex dashboard shows under Settings. The static hosting component handles SPA routing, hashed asset caching, and garbage collection of old builds. Deploys are atomic; clients subscribed through the deployment query can offer a refresh when a new build ships.
To seed production data once:
npx convex run demo:seedPublic --prodconvex/crons.ts registers a demo reset cron that wipes and reseeds every table every 10 minutes, because this repo powers a public demo. If you fork this to use as a real CRM, turn it off before you enter real data.
One command flips the workspace out of demo mode:
npx convex run demo:disableDemoMode # dev deployment
npx convex run demo:disableDemoMode --prod # production, if you deployedOnce demo mode is off, two things change. The reset handler becomes a no-op, so even if the cron still fires it wipes nothing and logs a skip to the Activity page. And writes start requiring a signed-in user, so wire up auth next (the /docs page on your deployment has a section on it).
The cron itself still fires every 10 minutes until you remove it, so delete the demo reset line from convex/crons.ts too. Keep the agent tick line; that one drives the agent task queue.
Prefer to hand the whole cleanup to a coding agent? Paste this into Cursor, Codex, Claude Code, or whatever you use:
I forked waynesutton/trycrm-convex and I am using it as a real CRM, not a public demo. Make sure my data is never wiped: run npx convex run demo:disableDemoMode on my dev deployment, and if I have a production deployment run it again with --prod. Then delete the demo reset cron line from convex/crons.ts and push. Leave the agent tick cron in place. Finish by confirming the workspace row has demoMode set to false.
- Companies, contacts, deals board, dashboard rollups, custom fields, timelines, all real time
- Notes and tasks on every company and contact: due dates, email reminders through the selected provider, complete buttons, all mirrored to the Activity page
- Compose email from any company or contact: a draggable, resizable window with To, Cc, Bcc, markdown preview, and attachments; the timeline records every send, and delivery waits for a Resend or AgentMail key
- Settings split into pages with a sub-sidebar: Team, Integrations, Slack, Email (provider, from identity, signature), AI provider, Sidebar, Custom fields
- Slack integration, off by default: event notifications with per-event toggles, a channel picker with search, a test button, and a
/crmslash command bot - Table sorting, filtering, and inline add rows on Companies and Contacts
- Deals as a drag and drop board plus a sortable list view
- Ask: a Claude-style workspace chat with streamed replies, slash commands (including
/taskand/note, which need no AI key), a thread sub-sidebar, archive, and delete - Command-K search backed by Convex full text search indexes on companies, contacts, and deals
- Activity: a live dashboard-style log of function outcomes with pause, select one or all, and clear
- Sidebar items reorder by drag and drop; Settings can hide items; the rail icon collapses the sidebar
- Agent task queue with leasing, workpools, and scheduled rechecks that require a reason
- Agents that build agents: describe a process, get a versioned draft definition
- Record chat with web research tools (Firecrawl, Exa, or Context.dev, any one key is enough) that answer honestly about missing keys
- AI provider picker: OpenAI, Claude, OpenRouter, DeepSeek, or Grok, none configured by default
- Dark and light themes with a toggle in the header and the sidebar footer
- Demo reset every 10 minutes via cron (the Activity log resets with it)
What is intentionally off in the demo: sign-in (the code is structured for Convex Auth; the demo keeps writes open on seeded data), outbound email (Resend and AgentMail are installed but no keys are set), and Slack (built in, but the demo never posts).
The app has a live comparison page at /compare and full setup docs at /docs. Short version:
| Area | trycompai/crm | This version |
|---|---|---|
| Hosting | Vercel plus a separate API | Convex static hosting |
| Database | Postgres with Prisma | Convex database |
| Realtime | Polling and invalidation | Reactive queries |
| Queue | Redis backed workers | Workpool components, queue as a table |
| Auth | Better Auth | Convex Auth ready, off in demo |
| Resend SDK calls | Resend or AgentMail components, switchable | |
| Web research | Not included | Firecrawl or Context.dev scraping and Exa or Context.dev search as agent tools; any one key works |
| AI providers | OpenAI | OpenAI, Claude, OpenRouter, DeepSeek, or Grok, switchable in Settings |
| Workspace chat | Per-record chat only | Ask page with streamed replies, slash commands, and thread history |
| Notes and tasks | Notes on records | Notes and tasks with due dates, reminders, and completion |
| Search | Per-table inputs | Command-K palette on full text search indexes |
| Observability | Server logs | Activity page streams function outcomes live |
| Services to run | Frontend, API, Postgres, Redis, workers | One Convex deployment |
| Package manager | bun, Turborepo | npm, single package |
convex/ Backend: schema, functions, components config, crons
convex/model/ Shared logic: access control, cascade deletes, seed data
src/app/ CRM screens: dashboard, companies, contacts, deals, ask, activity, agents, settings
src/pages/ Landing, compare, and docs pages
src/components/ Shared UI primitives, demo banner, theme toggle
public/ Static assets served with the app
docs/ Upstream docs and port instructions
files.md has a description of every file. changelog.md tracks changes.
MIT licensed, the same license as the upstream project. The product design, agent philosophy, and seed content come from Comp AI's CRM. This port swaps the infrastructure for Convex and its components.