CRM-Lite — Lightweight SQLite CRM for Small Business
Overview
A minimal, file-based CRM built on SQLite for non-technical small business owners. No server, no external dependencies, no auth complexity. Designed for agent-mediated use: the owner interacts via text message with the agent, which reads/writes this database.
When to Use
- Small business (1-10 employees) with no existing CRM
- Agent-mediated access — owners interact via chat/Telegram, not direct DB access
- Lead tracking from website forms, referrals, email inquiries
- Pipeline visibility — estimate sent → accepted/rejected → job scheduled → paid
- Communication log — every email, call, text logged automatically
Don't Use For
- Multi-user concurrent access with role-based permissions
- Complex sales methodologies (MEDDIC, Challenger, etc.)
- Real-time collaboration features
- Integration with enterprise SSO/SCIM
Core Features
Data Model (SQLite: crm.db)
customers — Core customer records
id (PK), name, email, phone, company, address, city, state, zip
lead_source, lead_status (new/qualified/estimate_sent/won/lost)
total_value, notes, created_at, updated_at
leads — Inbound leads before qualification
id, source (website/referral/email/phone), contact_name
contact_email, contact_phone, company, property_address
sqft, ceiling_height, service_type, urgency
status (new/qualified/estimate_ready/scheduled/lost)
assigned_to, created_at, updated_at
activities — Every touchpoint
id, customer_id (FK), lead_id (FK), activity_type (call/email/text/meeting/estimate_sent/estimate_accepted/estimate_rejected/job_scheduled/job_completed/invoice_sent/payment_received)
description, outcome, next_action, next_action_date
created_at, updated_at
estimates — Estimate lifecycle
id, customer_id, lead_id, estimate_number, amount
status (draft/sent/accepted/rejected/expired)
sent_date, accepted_date, rejected_date, expires_date
line_items (JSON), notes, created_at, updated_at
communications — Message log
id, customer_id, lead_id, direction (inbound/outbound)
channel (email/sms/telegram/phone), subject, body
sent_at, created_at
tasks — Action items for owners
id, customer_id, lead_id, title, description
due_date, priority (low/medium/high/urgent), status (pending/in_progress/done)
assigned_to, created_at, updated_at
Quick Start
import sqlite3
from pathlib import Path
DB_PATH = Path("~/.hermes/crm/crm.db").expanduser()
DB_PATH.parent.mkdir(parents=True, exist_ok=True)
def init_db():
conn = sqlite3.connect(DB_PATH)
conn.execute("""
CREATE TABLE IF NOT EXISTS customers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL, email TEXT, phone TEXT,
company TEXT, address TEXT, city TEXT, state TEXT, zip TEXT,
lead_source TEXT, lead_status TEXT DEFAULT 'new',
total_value REAL DEFAULT 0, notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
# ... other tables
conn.commit()
return conn
Agent Integration Patterns
Create Lead from Website Form
User: "New lead from website: John Smith, john@email.com, 555-555-0123, 123 Main St [CITY] [STATE] [ZIP], 2500 sqft, service request"
Agent: Creates lead record, sets status=qualified, creates task for the owner to review
Log Estimate Sent
User: "Sent estimate #EST-0001-0042 to John Smith for $2,850"
Agent: Updates estimate status=sent, creates activity, creates task for follow-up in 3 days
Weekly Pipeline Report (Cron)
Agent: Queries all leads/customers, summarizes by stage, sends to the owner via Telegram
Common Pitfalls
- Forgetting to create indexes — Add indexes on
customers.email, leads.status, activities.customer_id for query speed
- Not backing up — SQLite file is the entire DB; copy to S3/Drive daily via cron
- Schema drift — Use migrations (numbered SQL files) rather than ad-hoc ALTER TABLE
- Concurrent writes — SQLite handles concurrent reads, but writes serialize; keep transactions short
Verification Checklist
One-Shot Recipes
"New lead from referral"
conn = init_db()
lead_id = conn.execute("""
INSERT INTO leads (source, contact_name, contact_email, contact_phone, company, property_address, sqft, service_type, status)
VALUES ('referral', 'Jane Doe', 'jane@example.com', '555-555-0199', 'Example Realty', '456 Palm Ave [CITY] [STATE] [ZIP]', 3200, 'service_request', 'qualified')
""").lastrowid
conn.commit()
"Get pipeline summary for weekly report"
summary = conn.execute("""
SELECT
COUNT(CASE WHEN lead_status='new' THEN 1 END) as new_leads,
COUNT(CASE WHEN lead_status='estimate_sent' THEN 1 END) as estimates_out,
COUNT(CASE WHEN lead_status='won' THEN 1 END) as won,
SUM(CASE WHEN lead_status='won' THEN total_value ELSE 0 END) as revenue
FROM customers
""").fetchone()
1---2name: crm-lite3description: Use when tracking customer interactions, managing leads, or building a lightweight CRM for small businesses without dedicated CRM software. Stores contacts, leads, activities, estimates, and communications in local SQLite.4license: MIT5---67# CRM-Lite — Lightweight SQLite CRM for Small Business89## Overview10A minimal, file-based CRM built on SQLite for non-technical small business owners. No server, no external dependencies, no auth complexity. Designed for agent-mediated use: the owner interacts via text message with the agent, which reads/writes this database.1112## When to Use13- **Small business** (1-10 employees) with no existing CRM14- **Agent-mediated access** — owners interact via chat/Telegram, not direct DB access15- **Lead tracking** from website forms, referrals, email inquiries16- **Pipeline visibility** — estimate sent → accepted/rejected → job scheduled → paid17- **Communication log** — every email, call, text logged automatically1819## Don't Use For20- Multi-user concurrent access with role-based permissions21- Complex sales methodologies (MEDDIC, Challenger, etc.)22- Real-time collaboration features23- Integration with enterprise SSO/SCIM2425## Core Features2627### Data Model (SQLite: `crm.db`)281. **customers** — Core customer records29 - `id` (PK), `name`, `email`, `phone`, `company`, `address`, `city`, `state`, `zip`30 - `lead_source`, `lead_status` (new/qualified/estimate_sent/won/lost)31 - `total_value`, `notes`, `created_at`, `updated_at`32332. **leads** — Inbound leads before qualification34 - `id`, `source` (website/referral/email/phone), `contact_name`35 - `contact_email`, `contact_phone`, `company`, `property_address`36 - `sqft`, `ceiling_height`, `service_type`, `urgency`37 - `status` (new/qualified/estimate_ready/scheduled/lost)38 - `assigned_to`, `created_at`, `updated_at`39403. **activities** — Every touchpoint41 - `id`, `customer_id` (FK), `lead_id` (FK), `activity_type` (call/email/text/meeting/estimate_sent/estimate_accepted/estimate_rejected/job_scheduled/job_completed/invoice_sent/payment_received)42 - `description`, `outcome`, `next_action`, `next_action_date`43 - `created_at`, `updated_at`44454. **estimates** — Estimate lifecycle46 - `id`, `customer_id`, `lead_id`, `estimate_number`, `amount`47 - `status` (draft/sent/accepted/rejected/expired)48 - `sent_date`, `accepted_date`, `rejected_date`, `expires_date`49 - `line_items` (JSON), `notes`, `created_at`, `updated_at`50515. **communications** — Message log52 - `id`, `customer_id`, `lead_id`, `direction` (inbound/outbound)53 - `channel` (email/sms/telegram/phone), `subject`, `body`54 - `sent_at`, `created_at`55566. **tasks** — Action items for owners57 - `id`, `customer_id`, `lead_id`, `title`, `description`58 - `due_date`, `priority` (low/medium/high/urgent), `status` (pending/in_progress/done)59 - `assigned_to`, `created_at`, `updated_at`6061## Quick Start6263```python64import sqlite365from pathlib import Path6667DB_PATH = Path("~/.hermes/crm/crm.db").expanduser()68DB_PATH.parent.mkdir(parents=True, exist_ok=True)6970def init_db():71 conn = sqlite3.connect(DB_PATH)72 conn.execute("""73 CREATE TABLE IF NOT EXISTS customers (74 id INTEGER PRIMARY KEY AUTOINCREMENT,75 name TEXT NOT NULL, email TEXT, phone TEXT,76 company TEXT, address TEXT, city TEXT, state TEXT, zip TEXT,77 lead_source TEXT, lead_status TEXT DEFAULT 'new',78 total_value REAL DEFAULT 0, notes TEXT,79 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,80 updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP81 )82 """)83 # ... other tables84 conn.commit()85 return conn86```8788## Agent Integration Patterns8990### Create Lead from Website Form91```92User: "New lead from website: John Smith, john@email.com, 555-555-0123, 123 Main St [CITY] [STATE] [ZIP], 2500 sqft, service request"93Agent: Creates lead record, sets status=qualified, creates task for the owner to review94```9596### Log Estimate Sent97```98User: "Sent estimate #EST-0001-0042 to John Smith for $2,850"99Agent: Updates estimate status=sent, creates activity, creates task for follow-up in 3 days100```101102### Weekly Pipeline Report (Cron)103```104Agent: Queries all leads/customers, summarizes by stage, sends to the owner via Telegram105```106107## Common Pitfalls1081091. **Forgetting to create indexes** — Add indexes on `customers.email`, `leads.status`, `activities.customer_id` for query speed1102. **Not backing up** — SQLite file is the entire DB; copy to S3/Drive daily via cron1113. **Schema drift** — Use migrations (numbered SQL files) rather than ad-hoc ALTER TABLE1124. **Concurrent writes** — SQLite handles concurrent reads, but writes serialize; keep transactions short113114## Verification Checklist115116- [ ] `crm.db` created with all 6 tables117- [ ] Indexes on foreign keys and query columns118- [ ] Agent can: create lead, log activity, update estimate status, create task119- [ ] Weekly report cron outputs: new leads, estimates sent, conversion rate, pipeline value120- [ ] Backup script copies DB to offsite location daily121122## One-Shot Recipes123124### "New lead from referral"125```python126conn = init_db()127lead_id = conn.execute("""128 INSERT INTO leads (source, contact_name, contact_email, contact_phone, company, property_address, sqft, service_type, status)129 VALUES ('referral', 'Jane Doe', 'jane@example.com', '555-555-0199', 'Example Realty', '456 Palm Ave [CITY] [STATE] [ZIP]', 3200, 'service_request', 'qualified')130""").lastrowid131conn.commit()132```133134### "Get pipeline summary for weekly report"135```python136summary = conn.execute("""137 SELECT138 COUNT(CASE WHEN lead_status='new' THEN 1 END) as new_leads,139 COUNT(CASE WHEN lead_status='estimate_sent' THEN 1 END) as estimates_out,140 COUNT(CASE WHEN lead_status='won' THEN 1 END) as won,141 SUM(CASE WHEN lead_status='won' THEN total_value ELSE 0 END) as revenue142 FROM customers143""").fetchone()144```