Deploying Django-CRM
Django-CRM, rebranded as BottleCRM, is a full-featured customer relationship management system purpose-built for startups and small businesses that need enterprise capabilities without the enterprise complexity. This modern CRM combines a powerful Django REST API backend with a sleek SvelteKit frontend, offering everything from lead tracking and opportunity management to invoicing and customer support case handling. What sets BottleCRM apart is its sophisticated multi-tenant architecture powered by PostgreSQL Row-Level Security (RLS), ensuring complete data isolation between organizations—a feature typically found only in expensive enterprise platforms.
The platform tackles the real-world problem of teams juggling multiple disconnected tools. Instead of maintaining separate subscriptions for lead management, contact databases, support tickets, task tracking, and invoicing, BottleCRM consolidates these essential functions into a single, self-hosted application. The Django backend provides rock-solid reliability and scalability, while the SvelteKit frontend delivers a responsive, modern interface that feels snappy even with thousands of records. Built-in features like JWT authentication, team management with role-based access, comprehensive activity tracking, email integration via AWS SES, and background task processing with Celery make it production-ready from day one. Whether you’re a five-person startup tracking your first hundred leads or a growing company managing thousands of customer relationships across multiple teams, BottleCRM scales with you while keeping your data under your complete control.
Why Deploy Django-CRM on Klutch.sh?
Deploying Django-CRM on Klutch.sh offers several compelling advantages for CRM operations:
- Complete Data Sovereignty: Keep all customer data, leads, and business intelligence on infrastructure you control
- Enterprise Security: Multi-tenant architecture with PostgreSQL RLS provides bank-level data isolation
- Always-On Reliability: Keep your CRM accessible 24/7 without maintaining physical servers
- Automatic HTTPS: Secure all customer data transmission with built-in SSL certificates
- Persistent Storage: All leads, contacts, opportunities, and documents persist safely across deployments
- Scalable Infrastructure: Handle growth from startup to enterprise on the same platform
- Cost Effective: Eliminate per-user SaaS fees with self-hosted CRM
- Background Processing: Celery + Redis for async tasks like email campaigns and data imports
- API-First Architecture: Full REST API for integrations with other business tools
Prerequisites
Before deploying Django-CRM to Klutch.sh, ensure you have:
- A Klutch.sh account (sign up here)
- A GitHub account with a repository for your Django-CRM deployment
- Basic understanding of Docker and containerization
- Familiarity with Django and Python applications
- Understanding of PostgreSQL and database concepts
- Git installed on your local development machine
- Basic knowledge of environment variables and secrets management
- Optional: Experience with SvelteKit for frontend customization
Understanding Django-CRM Architecture
Django-CRM (BottleCRM) operates as a modern full-stack application with a clear separation between backend and frontend:
Backend Architecture:
- Django 5.x with Django REST Framework: Provides the API layer and business logic
- PostgreSQL with Row-Level Security (RLS): Enterprise-grade multi-tenant data isolation where each organization’s data is automatically filtered at the database level
- Redis: Powers caching and serves as the message broker for Celery
- Celery: Handles background tasks like bulk email sending, data imports, and report generation
- JWT Authentication: Secure token-based authentication for API access
- AWS S3: File storage for attachments and documents (configurable)
- AWS SES: Transactional email delivery (configurable)
Frontend Architecture:
- SvelteKit 2.x with Svelte 5: Modern reactive framework with excellent performance
- TailwindCSS 4: Utility-first styling for responsive design
- shadcn-svelte: Beautiful, accessible UI components
- Zod: Runtime schema validation for form handling
- Axios: HTTP client for API communication
Core CRM Modules:
- Leads: Track and manage sales leads through your pipeline
- Accounts: Manage company and organization records
- Contacts: Store and organize contact information with custom fields
- Opportunities: Track deals and sales opportunities with revenue forecasting
- Cases: Customer support case management with priority levels
- Tasks: Task management with calendar and Kanban board views
- Invoices: Create and manage invoices with customizable templates
The multi-tenant architecture means a single deployment can securely serve multiple organizations, with PostgreSQL RLS ensuring that queries automatically filter data by organization context—a powerful feature that eliminates the risk of data leakage between tenants.
Step 1: Prepare Your Repository
Let’s set up a GitHub repository for Django-CRM with a production-ready Dockerfile.
Create a new directory for your project:
mkdir django-crm-appcd django-crm-appgit initCreate a production-optimized Dockerfile:
FROM python:3.11-slim as backend-builder
# Install system dependenciesRUN apt-get update && apt-get install -y \ build-essential \ libpq-dev \ postgresql-client \ && rm -rf /var/lib/apt/lists/*
# Set working directoryWORKDIR /app
# Copy backend requirementsCOPY backend/requirements.txt .
# Install Python dependenciesRUN pip install --no-cache-dir -r requirements.txt
# Stage 2: Frontend buildFROM node:18-alpine as frontend-builder
WORKDIR /app/frontend
# Copy frontend package filesCOPY frontend/package.json frontend/pnpm-lock.yaml ./
# Install pnpm and dependenciesRUN npm install -g pnpm && pnpm install --frozen-lockfile
# Copy frontend sourceCOPY frontend/ ./
# Build frontendRUN pnpm run build
# Stage 3: Final production imageFROM python:3.11-slim
# Install runtime dependenciesRUN apt-get update && apt-get install -y \ libpq5 \ postgresql-client \ nginx \ supervisor \ && rm -rf /var/lib/apt/lists/*
# Create app userRUN useradd -m -u 1001 crm && \ mkdir -p /app /var/log/supervisor && \ chown -R crm:crm /app
WORKDIR /app
# Copy Python dependencies from builderCOPY --from=backend-builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packagesCOPY --from=backend-builder /usr/local/bin /usr/local/bin
# Copy backend applicationCOPY --chown=crm:crm backend/ ./backend/
# Copy built frontend from builderCOPY --from=frontend-builder /app/frontend/build ./frontend/buildCOPY --from=frontend-builder /app/frontend/package.json ./frontend/
# Configure nginxCOPY docker/nginx.conf /etc/nginx/sites-available/default
# Configure supervisorCOPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
# Create required directoriesRUN mkdir -p /app/media /app/static /app/logs && \ chown -R crm:crm /app/media /app/static /app/logs
# Collect static filesWORKDIR /app/backendRUN python manage.py collectstatic --noinput || true
# Expose portEXPOSE 8000
# Switch to app userUSER crm
# Start supervisorCMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]Create a .env.example file with all required configuration:
# Django ConfigurationSECRET_KEY=your-secret-key-change-in-productionDEBUG=FalseALLOWED_HOSTS=django-crm.klutch.sh,localhostCORS_ALLOWED_ORIGINS=https://django-crm.klutch.sh
# Database ConfigurationDATABASE_URL=postgresql://crm_user:your_password@localhost:5432/crm_dbDB_HOST=localhostDB_PORT=5432DB_NAME=crm_dbDB_USER=crm_userDB_PASSWORD=your_db_password
# Redis ConfigurationREDIS_URL=redis://localhost:6379/0CELERY_BROKER_URL=redis://localhost:6379/0CELERY_RESULT_BACKEND=redis://localhost:6379/1
# AWS Configuration (Optional)AWS_ACCESS_KEY_ID=AWS_SECRET_ACCESS_KEY=AWS_STORAGE_BUCKET_NAME=AWS_S3_REGION_NAME=us-east-1USE_S3=False
# Email Configuration (AWS SES or SMTP)EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackendEMAIL_HOST=smtp.gmail.comEMAIL_PORT=587EMAIL_USE_TLS=TrueEMAIL_HOST_USER=EMAIL_HOST_PASSWORD=DEFAULT_FROM_EMAIL=noreply@example.com
# JWT ConfigurationJWT_SECRET_KEY=your-jwt-secret-keyJWT_ALGORITHM=HS256JWT_EXPIRATION_DELTA=3600
# Application SettingsSITE_URL=https://django-crm.klutch.shFRONTEND_URL=https://django-crm.klutch.shDOMAIN=django-crm.klutch.sh
# Security SettingsSECURE_SSL_REDIRECT=TrueSESSION_COOKIE_SECURE=TrueCSRF_COOKIE_SECURE=TrueSECURE_HSTS_SECONDS=31536000
# Multi-tenancy RLSENABLE_RLS=TrueRLS_ROLE_PREFIX=crm_org_
# Celery SettingsCELERY_TASK_ALWAYS_EAGER=FalseCELERY_TASK_EAGER_PROPAGATES=False
# LoggingLOG_LEVEL=INFOCreate docker/nginx.conf:
upstream django_backend { server 127.0.0.1:8001;}
server { listen 8000; server_name _; client_max_body_size 20M;
# Security headers add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# API requests to Django location /api/ { proxy_pass http://django_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_redirect off; }
# Admin panel location /admin/ { proxy_pass http://django_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }
# Static files location /static/ { alias /app/static/; expires 30d; add_header Cache-Control "public, immutable"; }
# Media files location /media/ { alias /app/media/; expires 7d; add_header Cache-Control "public"; }
# Swagger/API documentation location /swagger-ui/ { proxy_pass http://django_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }
# Frontend (SvelteKit) location / { root /app/frontend/build; try_files $uri $uri/ /index.html; expires 1h; add_header Cache-Control "public"; }
# Health check endpoint location /health { access_log off; return 200 "healthy\n"; add_header Content-Type text/plain; }}Create docker/supervisord.conf:
[supervisord]nodaemon=trueuser=crmlogfile=/app/logs/supervisord.logpidfile=/tmp/supervisord.pid
[program:django]command=gunicorn crm.wsgi:application --bind 127.0.0.1:8001 --workers 4 --timeout 120 --access-logfile /app/logs/gunicorn-access.log --error-logfile /app/logs/gunicorn-error.logdirectory=/app/backendautostart=trueautorestart=truestdout_logfile=/app/logs/django.logstderr_logfile=/app/logs/django-error.log
[program:celery_worker]command=celery -A crm worker --loglevel=info --concurrency=2directory=/app/backendautostart=trueautorestart=truestdout_logfile=/app/logs/celery-worker.logstderr_logfile=/app/logs/celery-worker-error.logstopwaitsecs=600killasgroup=truepriority=998
[program:celery_beat]command=celery -A crm beat --loglevel=infodirectory=/app/backendautostart=trueautorestart=truestdout_logfile=/app/logs/celery-beat.logstderr_logfile=/app/logs/celery-beat-error.logstopwaitsecs=10priority=999
[program:nginx]command=/usr/sbin/nginx -g "daemon off;"autostart=trueautorestart=truestdout_logfile=/app/logs/nginx-access.logstderr_logfile=/app/logs/nginx-error.logCreate backend/requirements.txt:
Django==5.0.3djangorestframework==3.14.0django-cors-headers==4.3.1django-filter==24.1psycopg2-binary==2.9.9redis==5.0.1celery==5.3.6python-decouple==3.8PyJWT==2.8.0gunicorn==21.2.0whitenoise==6.6.0Pillow==10.2.0boto3==1.34.51django-storages==1.14.2drf-yasg==1.21.7Create a .dockerignore file:
.git.github.env.env.*!.env.example*.pyc__pycache__/*.sqlite3db.sqlite3media/static/.vscode.idea*.md!README.mdnode_modules/frontend/.svelte-kit/frontend/build/frontend/dist/.DS_Store*.logCreate a README.md with setup instructions:
# Django-CRM Deployment on Klutch.sh
Modern open-source CRM system with Django REST Framework backend and SvelteKit frontend.
## Features
- Lead and opportunity management- Contact and account tracking- Customer support case management- Task management with calendar views- Invoice creation and management- Multi-tenant architecture with RLS- JWT authentication- Background task processing with Celery- Email integration (AWS SES/SMTP)- Comprehensive REST API
## Local Development
### Backend Setup
\`\`\`bashcd backendpython -m venv venvsource venv/bin/activatepip install -r requirements.txtcp .env.example .env# Edit .env with your configurationpython manage.py migratepython manage.py createsuperuserpython manage.py runserver\`\`\`
### Frontend Setup
\`\`\`bashcd frontendpnpm installpnpm run dev\`\`\`
### Start Celery Worker
\`\`\`bashcelery -A crm worker --loglevel=info\`\`\`
## Deployment
This application is configured for deployment on Klutch.sh. See the deployment guide for details.
## Environment Variables
See `.env.example` for all required environment variables.
## License
MIT License - See LICENSE file for details.Clone the Django-CRM repository and copy necessary files:
# Clone the official repository (for reference)git clone https://github.com/MicroPyramid/Django-CRM.git temp-django-crm
# Copy backend and frontend directoriescp -r temp-django-crm/backend ./cp -r temp-django-crm/frontend ./
# Clean uprm -rf temp-django-crmInitialize Git and push to GitHub:
git add .git commit -m "Initial Django-CRM setup for Klutch.sh deployment"git branch -M maingit remote add origin https://github.com/YOUR_USERNAME/django-crm-app.gitgit push -u origin mainStep 2: Set Up PostgreSQL Database
Django-CRM requires PostgreSQL with Row-Level Security (RLS) enabled for multi-tenancy.
- Click Create New App
- App Name:
django-crm-db - Select: PostgreSQL 15
- Traffic Type: TCP
- Internal Port:
5432 - Mount Path:
/var/lib/postgresql/data - Size:
20GB - Host: Internal host provided by Klutch.sh
- Port:
8000(external),5432(internal) - Database:
crm_db - User:
crm_user - Password: Generated or set by you
In Klutch.sh dashboard, create a new PostgreSQL database:
Add persistent storage for the database:
Once deployed, note the connection details:
Connect to the database and enable RLS:
-- Create databaseCREATE DATABASE crm_db;
-- Connect to the database\c crm_db
-- Create user with proper permissionsCREATE USER crm_user WITH PASSWORD 'your_secure_password';GRANT ALL PRIVILEGES ON DATABASE crm_db TO crm_user;
-- Enable RLS on all CRM tables (after migrations)ALTER TABLE accounts_account ENABLE ROW LEVEL SECURITY;ALTER TABLE contacts_contact ENABLE ROW LEVEL SECURITY;ALTER TABLE leads_lead ENABLE ROW LEVEL SECURITY;ALTER TABLE opportunity_opportunity ENABLE ROW LEVEL SECURITY;ALTER TABLE cases_case ENABLE ROW LEVEL SECURITY;ALTER TABLE tasks_task ENABLE ROW LEVEL SECURITY;ALTER TABLE invoices_invoice ENABLE ROW LEVEL SECURITY;
-- Create RLS policies (example for accounts)CREATE POLICY account_isolation_policy ON accounts_account USING (org_id = current_setting('app.current_org_id')::uuid);
-- Grant permissions to crm_userGRANT ALL ON ALL TABLES IN SCHEMA public TO crm_user;GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO crm_user;Step 3: Set Up Redis for Caching and Celery
Django-CRM uses Redis for caching and as a Celery message broker.
- Click Create New App
- App Name:
django-crm-redis - Select: Redis 7
- Traffic Type: TCP
- Internal Port:
6379 - Mount Path:
/data - Size:
5GB - Format:
redis://django-crm-redis:6379/0
Create a Redis instance on Klutch.sh:
Add persistent storage:
Once deployed, note the Redis URL:
Step 4: Deploy Django-CRM to Klutch.sh
Now let’s deploy the main Django-CRM application.
- App Name:
django-crm - Git Source: Select GitHub
- Repository: Choose your
django-crm-apprepository - Branch:
main - Traffic Type: Select HTTP
- Internal Port:
8000 - Mount Path:
/app/media - Size:
50GB - Mount Path:
/app/static - Size:
10GB - Mount Path:
/app/logs - Size:
5GB - Clone your repository
- Build the multi-stage Docker image
- Start Django, Celery workers, and Nginx
- Assign a URL like
django-crm.klutch.sh
Log in to your Klutch.sh dashboard at klutch.sh/app.
Click Create New App and fill in the details:
Klutch.sh will automatically detect your Dockerfile and use it for deployment.
Configure the traffic settings:
Add persistent storage for media files and logs:
Volume 1 - Media Files:
Volume 2 - Static Files:
Volume 3 - Logs:
Add environment variables (from your .env file):
SECRET_KEY=your-long-random-secret-key-hereDEBUG=FalseALLOWED_HOSTS=django-crm.klutch.shCORS_ALLOWED_ORIGINS=https://django-crm.klutch.sh
DATABASE_URL=postgresql://crm_user:your_password@django-crm-db:5432/crm_dbDB_HOST=django-crm-dbDB_PORT=5432DB_NAME=crm_dbDB_USER=crm_userDB_PASSWORD=your_db_password
REDIS_URL=redis://django-crm-redis:6379/0CELERY_BROKER_URL=redis://django-crm-redis:6379/0CELERY_RESULT_BACKEND=redis://django-crm-redis:6379/1
EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackendEMAIL_HOST=smtp.gmail.comEMAIL_PORT=587EMAIL_USE_TLS=TrueEMAIL_HOST_USER=your-email@gmail.comEMAIL_HOST_PASSWORD=your-app-passwordDEFAULT_FROM_EMAIL=noreply@yourcompany.com
JWT_SECRET_KEY=your-jwt-secret-keyJWT_ALGORITHM=HS256JWT_EXPIRATION_DELTA=3600
SITE_URL=https://django-crm.klutch.shFRONTEND_URL=https://django-crm.klutch.shDOMAIN=django-crm.klutch.sh
SECURE_SSL_REDIRECT=TrueSESSION_COOKIE_SECURE=TrueCSRF_COOKIE_SECURE=TrueSECURE_HSTS_SECONDS=31536000
ENABLE_RLS=TrueRLS_ROLE_PREFIX=crm_org_
LOG_LEVEL=INFOClick Deploy to start the deployment process.
Wait for the deployment to complete. Klutch.sh will:
Once deployment succeeds, run database migrations by accessing the container:
# Connect to your app container via Klutch.sh terminalpython backend/manage.py migratepython backend/manage.py createsuperuserpython backend/manage.py collectstatic --noinputAccess your Django-CRM instance at django-crm.klutch.sh.
Step 5: Initial Configuration and Setup
After your first deployment, configure Django-CRM for production use.
- Navigate to Organizations in the admin
- Click Add Organization
- Enter organization details (name, domain, settings)
- Save the organization
- Go to Users in the admin panel
- Add team members with appropriate roles
- Assign users to teams
- Set permissions for each role
- Update
FRONTEND_URLenvironment variable - Ensure CORS settings allow your frontend domain
- Configure authentication tokens
Access the admin panel at https://django-crm.klutch.sh/admin and log in with your superuser credentials.
Create your first organization:
Set up Row-Level Security for the organization:
-- Connect to your database\c crm_db
-- Create RLS role for the organizationCREATE ROLE crm_org_abc123 NOINHERIT;
-- Grant role to crm_userGRANT crm_org_abc123 TO crm_user;
-- Set default organization contextALTER ROLE crm_org_abc123 SET app.current_org_id = 'organization-uuid-here';Configure email settings:
Using Gmail SMTP:
EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackendEMAIL_HOST=smtp.gmail.comEMAIL_PORT=587EMAIL_USE_TLS=TrueEMAIL_HOST_USER=your-email@gmail.comEMAIL_HOST_PASSWORD=your-app-specific-passwordDEFAULT_FROM_EMAIL=noreply@yourcompany.comUsing AWS SES:
EMAIL_BACKEND=django_ses.SESBackendAWS_SES_REGION_NAME=us-east-1AWS_SES_REGION_ENDPOINT=email.us-east-1.amazonaws.comAWS_ACCESS_KEY_ID=your-aws-access-keyAWS_SECRET_ACCESS_KEY=your-aws-secret-keyDEFAULT_FROM_EMAIL=noreply@yourcompany.comTest email configuration:
# In Django shellfrom django.core.mail import send_mail
send_mail( 'Test Email', 'This is a test email from Django-CRM', 'noreply@yourcompany.com', ['recipient@example.com'], fail_silently=False,)Create your first users and teams:
Configure frontend settings:
Access the frontend at https://django-crm.klutch.sh and complete the onboarding flow.
Step 6: Configure CRM Modules and Workflows
Customize Django-CRM for your business processes.
- Configure company branding
- Set payment terms and conditions
- Add tax rates and discount rules
- Configure invoice numbering
- Welcome emails for new contacts
- Follow-up sequences for leads
- Task reminders
- Invoice notifications
- Case status updates
Configure Lead Pipeline:
# In Django admin or via APILEAD_STAGES = [ 'New', 'Contacted', 'Qualified', 'Proposal Sent', 'Negotiation', 'Closed Won', 'Closed Lost']
# Set up lead sourcesLEAD_SOURCES = [ 'Website', 'Referral', 'Cold Call', 'Trade Show', 'Social Media', 'Email Campaign']Set up Opportunity Stages:
OPPORTUNITY_STAGES = [ {'name': 'Prospecting', 'probability': 10}, {'name': 'Qualification', 'probability': 20}, {'name': 'Needs Analysis', 'probability': 40}, {'name': 'Proposal', 'probability': 60}, {'name': 'Negotiation', 'probability': 80}, {'name': 'Closed Won', 'probability': 100}, {'name': 'Closed Lost', 'probability': 0}]Configure Case Priority Levels:
CASE_PRIORITIES = [ 'Low', 'Medium', 'High', 'Urgent']
CASE_STATUSES = [ 'New', 'In Progress', 'Waiting on Customer', 'Waiting on Third Party', 'Resolved', 'Closed']Set up Invoice Templates:
Create custom invoice templates in the admin panel:
Configure Task Types and Categories:
TASK_TYPES = [ 'Call', 'Email', 'Meeting', 'To-Do', 'Follow-up']
TASK_PRIORITIES = [ 'Low', 'Normal', 'High', 'Urgent']Set up Custom Fields:
Django-CRM allows custom fields for each module:
# Add custom fields via Django admin# Example: Add "Industry" field to Accounts{ 'field_name': 'industry', 'field_type': 'choice', 'choices': ['Technology', 'Healthcare', 'Finance', 'Retail', 'Manufacturing'], 'required': False, 'module': 'accounts'}Configure Email Templates:
Create email templates for automated communications:
Step 7: Set Up Background Tasks and Automation
Configure Celery for automated tasks and workflows.
- View task history
- Check task status (pending, success, failure)
- Retry failed tasks
- Schedule one-time tasks
Schedule Periodic Tasks:
# In backend/crm/celery.pyfrom celery.schedules import crontab
app.conf.beat_schedule = { 'send-daily-lead-digest': { 'task': 'leads.tasks.send_daily_digest', 'schedule': crontab(hour=9, minute=0), # 9 AM daily }, 'update-opportunity-stages': { 'task': 'opportunity.tasks.auto_update_stages', 'schedule': crontab(hour='*/6'), # Every 6 hours }, 'send-task-reminders': { 'task': 'tasks.tasks.send_reminders', 'schedule': crontab(hour='*', minute=0), # Every hour }, 'cleanup-old-activities': { 'task': 'common.tasks.cleanup_activities', 'schedule': crontab(hour=2, minute=0, day_of_week=0), # Sunday 2 AM },}Create Custom Celery Tasks:
from celery import shared_taskfrom django.core.mail import send_mailfrom .models import Lead
@shared_taskdef send_lead_notification(lead_id): """Send notification when new lead is created""" lead = Lead.objects.get(id=lead_id)
send_mail( f'New Lead: {lead.title}', f'A new lead has been created: {lead.first_name} {lead.last_name}', 'noreply@yourcompany.com', [lead.assigned_to.email], fail_silently=False, )
@shared_taskdef bulk_import_leads(file_path, org_id): """Import leads from CSV file""" import csv from .models import Lead
with open(file_path, 'r') as file: reader = csv.DictReader(file) for row in reader: Lead.objects.create( organization_id=org_id, first_name=row['first_name'], last_name=row['last_name'], email=row['email'], company=row['company'], status='New' )Set up Email Campaigns:
@shared_taskdef send_bulk_email_campaign(campaign_id): """Send email campaign to contacts""" from contacts.models import Contact from campaigns.models import Campaign
campaign = Campaign.objects.get(id=campaign_id) contacts = campaign.target_contacts.all()
for contact in contacts: send_mail( campaign.subject, campaign.body, campaign.from_email, [contact.email], html_message=campaign.html_body, fail_silently=True, )
# Track send campaign.track_send(contact)Configure Task Monitoring:
Monitor Celery tasks in the Django admin:
Set up Webhooks for Integrations:
@shared_taskdef send_webhook(webhook_url, event_type, payload): """Send webhook notification""" import requests
try: response = requests.post( webhook_url, json={ 'event': event_type, 'data': payload, 'timestamp': datetime.now().isoformat() }, headers={'Content-Type': 'application/json'}, timeout=10 ) return response.status_code == 200 except requests.exceptions.RequestException: return FalseStep 8: Implement Security Best Practices
Harden your Django-CRM deployment for production.
Enable Rate Limiting:
REST_FRAMEWORK = { 'DEFAULT_THROTTLE_CLASSES': [ 'rest_framework.throttling.AnonRateThrottle', 'rest_framework.throttling.UserRateThrottle' ], 'DEFAULT_THROTTLE_RATES': { 'anon': '100/hour', 'user': '1000/hour' }}Configure CORS Properly:
# Only allow your frontend domainCORS_ALLOWED_ORIGINS = [ 'https://django-crm.klutch.sh',]
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOWED_METHODS = [ 'DELETE', 'GET', 'OPTIONS', 'PATCH', 'POST', 'PUT',]Set Security Headers:
SECURE_BROWSER_XSS_FILTER = TrueSECURE_CONTENT_TYPE_NOSNIFF = TrueX_FRAME_OPTIONS = 'DENY'SECURE_HSTS_SECONDS = 31536000SECURE_HSTS_INCLUDE_SUBDOMAINS = TrueSECURE_HSTS_PRELOAD = TrueImplement IP Whitelisting (optional):
from django.core.exceptions import PermissionDenied
class IPWhitelistMiddleware: def __init__(self, get_response): self.get_response = get_response self.whitelist = [ '192.168.1.0/24', '10.0.0.0/8', ]
def __call__(self, request): ip = self.get_client_ip(request) if not self.is_whitelisted(ip): raise PermissionDenied return self.get_response(request)Enable Audit Logging:
# Track all important actionsfrom django.db.models.signals import post_save, post_delete
@receiver(post_save, sender=Lead)def log_lead_changes(sender, instance, created, **kwargs): action = 'created' if created else 'updated' AuditLog.objects.create( user=instance.modified_by, action=action, model='Lead', object_id=instance.id, changes=instance.get_changes() )Set up Two-Factor Authentication:
# Install django-otp# pip install django-otp qrcode
INSTALLED_APPS += [ 'django_otp', 'django_otp.plugins.otp_totp',]
MIDDLEWARE += [ 'django_otp.middleware.OTPMiddleware',]Regular Security Audits:
# Check for security issuespython manage.py check --deploy
# Update dependencies regularlypip list --outdatedpip install -U django djangorestframeworkCommon Use Cases
Django-CRM excels in various business scenarios:
Sales Team Management
Pipeline View:- Track leads through stages- Forecast revenue from opportunities- Monitor team performance- Automate follow-up tasks- Generate sales reportsCustomer Support
Case Management:- Track support tickets by priority- Assign cases to team members- Monitor SLA compliance- Customer communication history- Knowledge base integrationSmall Business CRM
All-in-One Solution:- Manage customer contacts- Track sales opportunities- Send invoices and track payments- Schedule tasks and appointments- Email marketing campaignsMulti-Tenant SaaS
Enterprise Features:- Complete data isolation with RLS- Organization-specific customization- Role-based access control- Per-tenant usage tracking- White-label capabilitiesTroubleshooting
Database Connection Issues
Problem: Django can’t connect to PostgreSQL
Solution:
# Check database service is running# In Klutch.sh dashboard, verify django-crm-db is active
# Test connection from Django containerpsql -h django-crm-db -U crm_user -d crm_db
# Verify DATABASE_URL formatDATABASE_URL=postgresql://crm_user:password@django-crm-db:5432/crm_dbCelery Workers Not Processing Tasks
Problem: Background tasks are not executing
Solution:
# Check Redis connectionredis-cli -h django-crm-redis ping
# Verify Celery is running in supervisorsupervisorctl status celery_worker
# Check Celery logstail -f /app/logs/celery-worker.log
# Restart Celery if neededsupervisorctl restart celery_workerRLS Policies Not Working
Problem: Users can see data from other organizations
Solution:
-- Verify RLS is enabledSELECT tablename, rowsecurityFROM pg_tablesWHERE schemaname = 'public';
-- Check policy existsSELECT * FROM pg_policiesWHERE tablename = 'accounts_account';
-- Test policySET app.current_org_id = 'org-uuid';SELECT * FROM accounts_account;Static Files Not Loading
Problem: CSS/JS files return 404 errors
Solution:
# Collect static filespython backend/manage.py collectstatic --noinput
# Check nginx configurationnginx -t
# Verify static directory permissionsls -la /app/static/
# Restart nginxsupervisorctl restart nginxEmail Sending Fails
Problem: Emails are not being sent
Solution:
# Test email configurationfrom django.core.mail import send_mail
send_mail( 'Test', 'Test message', 'noreply@yourcompany.com', ['test@example.com'],)
# Check email backend settingsprint(settings.EMAIL_BACKEND)print(settings.EMAIL_HOST)
# View email logstail -f /app/logs/django.log | grep emailFrontend Build Fails
Problem: SvelteKit build errors during deployment
Solution:
# Check Node.js versionnode --version # Should be 18+
# Clear pnpm cachepnpm store prune
# Rebuild frontendcd frontendrm -rf node_modules .svelte-kit buildpnpm installpnpm run build
# Check for TypeScript errorspnpm run checkMemory Issues with Large Datasets
Problem: Application crashes with large imports
Solution:
# Use chunked processing for importsfrom django.db import transaction
@shared_taskdef import_large_dataset(file_path): CHUNK_SIZE = 1000 records = []
for i, row in enumerate(read_csv(file_path)): records.append(create_record(row))
if i % CHUNK_SIZE == 0: with transaction.atomic(): Lead.objects.bulk_create(records) records = []
# Import remaining records if records: Lead.objects.bulk_create(records)JWT Token Expiration
Problem: Users getting logged out frequently
Solution:
# Increase JWT expiration timeJWT_EXPIRATION_DELTA = 86400 # 24 hours
# Implement token refreshfrom rest_framework_simplejwt.views import TokenRefreshView
urlpatterns = [ path('api/token/refresh/', TokenRefreshView.as_view()),]Advanced Configuration
Custom API Endpoints
from rest_framework.decorators import api_viewfrom rest_framework.response import Response
@api_view(['GET'])def sales_dashboard(request): """Custom sales dashboard endpoint""" org_id = request.user.organization_id
return Response({ 'total_leads': Lead.objects.filter(organization_id=org_id).count(), 'open_opportunities': Opportunity.objects.filter( organization_id=org_id, stage__in=['Proposal', 'Negotiation'] ).count(), 'revenue_forecast': calculate_forecast(org_id), 'conversion_rate': calculate_conversion_rate(org_id), })Webhooks Integration
from django.dispatch import receiverfrom django.db.models.signals import post_savefrom leads.models import Lead
@receiver(post_save, sender=Lead)def trigger_lead_webhook(sender, instance, created, **kwargs): if created: webhooks = Webhook.objects.filter( organization=instance.organization, event='lead.created', active=True )
for webhook in webhooks: send_webhook.delay( webhook.url, 'lead.created', instance.to_dict() )Multi-Language Support
from django.utils.translation import gettext_lazy as _
LANGUAGES = [ ('en', _('English')), ('es', _('Spanish')), ('fr', _('French')), ('de', _('German')),]
LOCALE_PATHS = [ os.path.join(BASE_DIR, 'locale'),]
# Generate translations# python manage.py makemessages -l es# python manage.py compilemessagesCustom Reporting
from django.db.models import Count, Sum, Avgfrom django.utils import timezonefrom datetime import timedelta
def generate_monthly_report(org_id): last_month = timezone.now() - timedelta(days=30)
report = { 'leads': { 'total': Lead.objects.filter( organization_id=org_id, created_at__gte=last_month ).count(), 'by_source': Lead.objects.filter( organization_id=org_id, created_at__gte=last_month ).values('source').annotate(count=Count('id')), }, 'opportunities': { 'total_value': Opportunity.objects.filter( organization_id=org_id, created_at__gte=last_month ).aggregate(Sum('amount'))['amount__sum'], 'win_rate': calculate_win_rate(org_id, last_month), }, 'activities': { 'total': Activity.objects.filter( organization_id=org_id, created_at__gte=last_month ).count(), 'by_type': Activity.objects.filter( organization_id=org_id, created_at__gte=last_month ).values('type').annotate(count=Count('id')), } }
return reportProduction Best Practices
Database Optimization
# Use select_related and prefetch_relatedleads = Lead.objects.select_related( 'assigned_to', 'created_by').prefetch_related( 'tags', 'attachments')
# Add database indexesclass Lead(models.Model): email = models.EmailField(db_index=True) status = models.CharField(max_length=50, db_index=True)
class Meta: indexes = [ models.Index(fields=['organization', 'created_at']), models.Index(fields=['organization', 'status']), ]Caching Strategy
CACHES = { 'default': { 'BACKEND': 'django_redis.cache.RedisCache', 'LOCATION': 'redis://django-crm-redis:6379/2', 'OPTIONS': { 'CLIENT_CLASS': 'django_redis.client.DefaultClient', } }}
# Cache expensive queriesfrom django.core.cache import cache
def get_dashboard_stats(org_id): cache_key = f'dashboard_stats_{org_id}' stats = cache.get(cache_key)
if stats is None: stats = calculate_dashboard_stats(org_id) cache.set(cache_key, stats, 300) # 5 minutes
return statsMonitoring and Logging
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'verbose': { 'format': '{levelname} {asctime} {module} {message}', 'style': '{', }, }, 'handlers': { 'file': { 'level': 'INFO', 'class': 'logging.handlers.RotatingFileHandler', 'filename': '/app/logs/django.log', 'maxBytes': 1024 * 1024 * 10, # 10 MB 'backupCount': 5, 'formatter': 'verbose', }, 'celery': { 'level': 'INFO', 'class': 'logging.handlers.RotatingFileHandler', 'filename': '/app/logs/celery.log', 'maxBytes': 1024 * 1024 * 10, 'backupCount': 5, 'formatter': 'verbose', }, }, 'loggers': { 'django': { 'handlers': ['file'], 'level': 'INFO', 'propagate': True, }, 'celery': { 'handlers': ['celery'], 'level': 'INFO', 'propagate': True, }, },}Backup Strategy
#!/bin/bash# backup-crm.sh - Daily backup script
BACKUP_DIR="/backups"TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# Backup databasepg_dump -h django-crm-db -U crm_user crm_db | gzip > "${BACKUP_DIR}/db-backup-${TIMESTAMP}.sql.gz"
# Backup media filestar -czf "${BACKUP_DIR}/media-backup-${TIMESTAMP}.tar.gz" /app/media
# Backup uploaded filestar -czf "${BACKUP_DIR}/uploads-backup-${TIMESTAMP}.tar.gz" /app/uploads
# Keep only last 30 daysfind "${BACKUP_DIR}" -name "*.gz" -mtime +30 -delete
echo "Backup completed: ${TIMESTAMP}"Scaling Considerations
Horizontal Scaling
For high-traffic deployments:
- Multiple Application Instances: Deploy multiple Django-CRM containers behind a load balancer
- Dedicated Celery Workers: Run separate containers for Celery workers with different concurrency settings
- Database Connection Pooling: Use PgBouncer for connection pooling
- CDN for Static Assets: Serve static files through a CDN
- Read Replicas: Set up PostgreSQL read replicas for reporting queries
Performance Optimization
# Use database connection poolingDATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'crm_db', 'USER': 'crm_user', 'PASSWORD': 'password', 'HOST': 'django-crm-db', 'PORT': '5432', 'CONN_MAX_AGE': 600, 'OPTIONS': { 'connect_timeout': 10, } }}
# Enable query optimizationMIDDLEWARE = [ 'django.middleware.cache.UpdateCacheMiddleware', # ... other middleware 'django.middleware.cache.FetchFromCacheMiddleware',]Conclusion
You’ve successfully deployed Django-CRM (BottleCRM) on Klutch.sh, creating a comprehensive customer relationship management system with enterprise-grade features. Your setup includes:
- ✅ Full-stack Django REST API backend with SvelteKit frontend
- ✅ Multi-tenant architecture with PostgreSQL Row-Level Security
- ✅ Background task processing with Celery and Redis
- ✅ Persistent storage for media, static files, and logs
- ✅ Email integration for automated communications
- ✅ JWT authentication and role-based access control
- ✅ Complete CRM modules (leads, contacts, accounts, opportunities, cases, tasks, invoices)
- ✅ API documentation with Swagger UI
- ✅ Production-ready security configurations
Django-CRM provides everything startups and small businesses need to manage customer relationships effectively, from tracking leads through your sales pipeline to handling support cases and sending invoices. The multi-tenant architecture means you can serve multiple organizations from a single deployment, while PostgreSQL RLS ensures complete data isolation and security.
For more information about Django-CRM features and customization options, visit the official GitHub repository and documentation.