The Problem

Sales teams need a lightweight, extensible CRM that can be self‑hosted and integrated with existing telephony (Twilio, Exotel) and social (Facebook) channels. Off‑the‑shelf SaaS options lock data behind subscriptions and limit custom business logic.

What This Does

crm/ implements the server side on the Frappe/Python stack, exposing REST‑like methods in crm/api/*.py (e.g., crm/api/doc.py, crm/api/user.py). The Vue front‑end lives under frontend/ with entry files frontend/index.html, frontend/src/main.js, and utility helpers in frontend/src/utils/index.js. Core data objects such as leads, deals, and call logs are defined as Frappe DocTypes in crm/fcrm/doctype/ (e.g., crm/fcrm/doctype/crm_deal/crm_deal.py). Integration points for Twilio and Exotel are in crm/integrations/, and Facebook lead sync lives in crm/lead_syncing/doctype/lead_sync_source/facebook.py.

How It Is Wired

Execution begins in the Frappe request handler, which eventually routes to the custom endpoints defined in crm/integrations/exotel/handler.py. The two most‑used entry functions are:

  • handle_request (line 21) – processes inbound webhook calls, updates the DB via frappe.db.commit, and calls update_call_log.
  • make_a_call (line 71) – initiates an outbound call through the Exotel API (requests.get in get_all_exophones) and records the action with create_call_log.

Both paths converge on shared utilities:

  • create_call_log – writes a crm_call_log DocType (DB write).
  • update_call_log – reads the call, then contacts Exotel (self.twilio_client.calls(...).fetch) for status updates.

The most widely referenced functions (≥ 3 callers) are get_base_currency_symbol, sync_leads_from_all_enabled_sources, and _process_event_notifications_by_interval. They sit in crm/api/doc.py and crm/lead_syncing/ and drive database‑side calculations and periodic sync jobs.

The import graph shows three modules in a cycle (crm/install.py, crm/fcrm/doctype/fcrm_settings/fcrm_settings.py in both JS and Python). This adds fragility: a change in any of the three forces a re‑evaluation of all dependent modules (222 internal modules, 55 import edges). The most connected DocType is crm/fcrm/doctype/crm_deal/crm_deal.py (25 functions, referenced from four other files) and is a hotspot for future changes.

How To Use It

# Clone the repository
git clone https://github.com/moses-y/crm.git
cd crm

# Build the development container (Docker Compose files are provided)
docker compose -f .devcontainer/docker-compose.yml up -d --build

# Install Python dependencies
pip install -r requirements.txt   # requirements.txt is generated from pyproject.toml

# Install front‑end dependencies
yarn install --cwd frontend

# Run the Frappe development server (as defined by the Docker entrypoint)
docker compose -f docker/docker-compose.yml up -d

# The web UI is available at http://localhost:8000

Configuration files are docker/docker-compose.yml (service definitions) and frontend/tailwind.config.js / frontend/vite.config.js for the UI build. Environment variables for Twilio, Exotel, and Facebook are read by the respective integration modules (crm/integrations/twilio/twilio_handler.py, crm/integrations/exotel/handler.py, crm/lead_syncing/doctype/lead_sync_source/facebook.py) but the repo does not ship a template; users must create .env entries referenced in those modules.

Real‑World Use

A sales team can deploy the stack on an internal server, enable the Twilio integration by adding TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN to the container’s environment, and then configure a webhook URL in Twilio pointing to https://<host>/api/method/crm.integrations.twilio.handler.handle_request. Incoming calls are logged via create_call_log, and agents can view and update lead status through the Vue Kanban board in frontend/src/components/ViewControls.vue.

Code Health & Issues

Static analysis (deterministic) reports the following measured concerns (all from the repository’s own files):

  • High – Import cycle – crm/install.py, crm/fcrm/doctype/fcrm_settings/fcrm_settings.js, crm/fcrm/doctype/fcrm_settings/fcrm_settings.py. Break the cycle by extracting shared symbols or deferring imports.
  • High – Deep nesting – 31 files (e.g., crm/install.py, crm/utils/__init__.py). Refactor with early returns or guard clauses.
  • High – Duplicated code – 2151 repeated 6‑line blocks across 251 files (e.g., crm/api/user.py, crm/fcrm/doctype/crm_invitation/crm_invitation.py). Extract helpers to a common module.
  • High – Oversized file – 11 files > 1000 lines (e.g., frontend/src/components/ViewControls.vue, frontend/src/utils/index.js, crm/api/doc.py). Split by responsibility.
  • Medium – Broad exception handling – 6 files swallow all exceptions (e.g., crm/lead_syncing/doctype/lead_sync_source/facebook.py). Catch specific errors.
  • Medium – File opened without context manager – crm/fcrm/doctype/erpnext_crm_settings/erpnext_crm_settings.py. Use with open(...).
  • Medium – High branching density – 7 files (e.g., frontend/src/utils/index.js). Reduce decision complexity.

CI‑related audit (9 findings):

  • High – Pin GitHub Actions – .github/workflows/* uses version tags (@v3, @v2). Replace with commit SHAs.
  • High – CI never runs tests – workflows lack a test step despite 34 test files. Add a run: yarn test or pytest step.
  • Medium – Least‑privilege GITHUB_TOKEN – .github/workflows/builds.yml declares no permissions. Add explicit contents: read.
  • Medium – No Dependabot/Renovate – add .github/dependabot.yml.
  • Medium – No vulnerability scan – integrate dependency-review-action or osv-scanner.
  • Medium – Persist‑credentials true – set persist-credentials: false on checkout.
  • Medium – Post‑install script – package.json defines a postinstall. Move work to an explicit build step or disable scripts in CI.
  • Low – Missing job timeout – add timeout-minutes to each workflow job.
  • Low – Missing repository conventions – add .editorconfig, .gitattributes, and a formatter config.

All findings are derived from static analysis; no additional subjective issues are introduced.

The Bottom Line

The repo provides a full‑stack, open‑source CRM built on the Frappe framework with a modern Vue front‑end and solid telephony integrations. It is functional but suffers from import cycles, deep nesting, duplicated logic, and several CI hygiene gaps that increase maintenance risk. Teams comfortable with Python/Frappe and willing to refactor the identified hotspots can adopt it for self‑hosted sales workflows; others may prefer a more polished, actively maintained alternative.