Conversations, contacts, inboxes and messaging backend for the Evo CRM Community.
Website · Documentation · Community · Support
Evo CRM Backend is the core API of the Evo CRM Community customer support platform. Built with Ruby on Rails 7.1 (API mode), it manages conversations, contacts, messages, inboxes, and integrations across multiple communication channels (WhatsApp, Email, Web Widget, and more).
It exposes a comprehensive RESTful API and supports real-time messaging via ActionCable WebSockets.
Evo CRM Backend is part of the Evo CRM Community ecosystem maintained by Evolution Foundation. To use the full stack, clone the umbrella repository with submodules:
git clone --recurse-submodules git@github.com:evolution-foundation/evo-crm-community.gitThe Community Edition is single-tenant by design — one account, no multi-tenancy overhead, no super-admin, no billing or plans. All limits are removed and features are unlocked by default.
| Component | Technology |
|---|---|
| Backend | Ruby on Rails 7.1 (API mode) |
| Ruby | 3.4.4 |
| Database | PostgreSQL |
| Cache & Jobs | Redis + Sidekiq |
| Real-time | ActionCable (WebSocket) |
| Authentication | Bearer token via evo-auth-service-community |
| File storage | AWS S3, Google Cloud Storage, Azure Blob |
- Ruby 3.4.4
- PostgreSQL 12+
- Redis 6+
- pnpm (optional, for convenience scripts)
git clone git@github.com:evolution-foundation/evo-ai-crm-community.git
cd evo-ai-crm-community
# Install dependencies
bundle install
pnpm install # Optional — for convenience scripts
# Setup database
bundle exec rails db:setup
bundle exec rails db:migrate# Using pnpm (recommended)
pnpm dev # Rails + Sidekiq
pnpm start # Rails only
pnpm sidekiq # Sidekiq only
# Using Overmind / Foreman
overmind start -f Procfile.dev
# Manual
bundle exec rails server -p 3000
bundle exec sidekiq -C config/sidekiq.ymlThe API will be available at http://localhost:3000.
Copy .env.example to .env and configure:
# Database
DATABASE_URL=postgresql://localhost/evolution_crm_development
# Redis
REDIS_URL=redis://localhost:6379/0
# Optional: ScyllaDB for high-performance message storage
SCYLLA_ENABLED=true
SCYLLA_HOSTS=localhost
SCYLLA_PORT=9042
SCYLLA_KEYSPACE=evo_crm
# Frontend URL (CORS)
FRONTEND_URL=http://localhost:8080
# Storage (S3, GCS, Azure)
ACTIVE_STORAGE_SERVICE=localSee .env.example for the full list.
| Script | Description |
|---|---|
pnpm dev |
Start Rails + Sidekiq |
pnpm start |
Start Rails server |
pnpm test |
Run RSpec tests |
pnpm lint |
Run RuboCop |
pnpm lint:fix |
Run RuboCop with auto-fix |
pnpm db:setup |
Setup database |
pnpm db:migrate |
Run migrations |
pnpm db:seed |
Seed database |
pnpm console |
Rails console |
pnpm sidekiq |
Start Sidekiq worker |
The application runs in Rails API mode — no frontend views. The frontend is developed separately (evo-ai-frontend-community).
Business logic is organized in service objects:
app/services/
├── base/
│ └── send_on_channel_service.rb
├── whatsapp/
│ └── message_processor_service.rb
└── crm/
└── contact_sync_service.rb
Domain events are published for cross-cutting concerns:
contact.createdconversation.resolvedmessage.sent
Heavy operations run asynchronously: external API calls, webhook processing, bulk operations.
http://localhost:3000/api/v1
Bearer tokens issued by evo-auth-service-community:
curl -H "Authorization: Bearer YOUR_TOKEN" \
http://localhost:3000/api/v1/conversationsSwagger UI available at http://localhost:3000/swagger.
For full API documentation, see docs.evolutionfoundation.com.br.
- Real-time messaging via ActionCable WebSockets
- Multi-channel support (WhatsApp, Email, Web Widget, and more)
- RESTful API with comprehensive Swagger documentation
- High-performance messages with optional ScyllaDB (<1ms latency)
- Background jobs with Sidekiq
- Event-driven architecture for extensibility
- File storage support for S3, GCS, Azure Blob
- Rich message templates with drag-and-drop editor
# All tests
pnpm test
# or
bundle exec rspec
# Specific file
bundle exec rspec spec/models/contact_spec.rb
# Specific test
bundle exec rspec spec/models/contact_spec.rb:42# Check
pnpm lint
# or
bundle exec rubocop
# Auto-fix
pnpm lint:fix
# or
bundle exec rubocop -adocker-compose build
docker-compose up
# Specific services
docker-compose up backend workerPorts channel-coupled message templates (rows with channel_id NOT NULL) into the
global/independent flow introduced by EVO-1231, creating a channel-less
(channel_id IS NULL) counterpart for each. WhatsApp Cloud templates are
intentionally not migrated — Meta requires an approved template tied to a WABA
channel, so they stay channel-bound.
# 1. Preview — logs the counts that would migrate, writes nothing
DRY_RUN=true bundle exec rake templates:migrate_legacy
# 2. (Recommended) back up the table before applying in production
pg_dump -t message_templates "$DATABASE_URL" > message_templates_backup.sql
# 3. Apply — idempotent, safe to rerun (provenance tracked via external_legacy_id)
bundle exec rake templates:migrate_legacy
# Rollback — deletes ONLY migrated globals (external_legacy_id IS NOT NULL);
# channel-bound originals and admin-created globals are untouched.
bundle exec rake templates:rollback_legacy_migrationNotes:
- Idempotent: rerunning never duplicates (each copy carries
external_legacy_id = "message_template:<source_id>", backed by a partial unique index). - Rollback caveat: rollback also discards any admin edits made to migrated templates after the migration ran.
- Best run after the template menu UI (EVO-1233) is available so an admin can visually validate the migrated globals.
- The migration emits Prometheus counters (
templates_migrated_total{source},templates_migrated_skipped{reason}), but the authoritative result is the summary printed by the rake task and the structured log lines — the counters increment in the worker process and are not exposed to the web/metricsscrape.
| Resource | Link |
|---|---|
| Website | evolutionfoundation.com.br |
| Documentation | docs.evolutionfoundation.com.br |
| Community | evolutionfoundation.com.br/community |
| Swagger | http://localhost:3000/swagger |
| Changelog | CHANGELOG.md |
| Contributing | CONTRIBUTING.md |
| Security | SECURITY.md |
The authoritative list is lib/events/evo_flow_event_names.rb (EvoFlow::EVENT_NAMES, 16 dot-notation strings, frozen). EvoFlow::PayloadBuilder.build_track and .build_identify raise EvoFlow::InvalidEventName if a caller passes a name outside the list. The same list is mirrored in evo-flow/src/modules/events/event-names.enum.ts, and a CI script (scripts/check-event-names-sync.sh at the monorepo root) blocks PRs that drift.
Growing the list: adding or removing an entry requires three coordinated edits in the same PR: lib/events/evo_flow_event_names.rb (this repo), src/modules/events/event-names.enum.ts (in evo-flow), and the EXPECTED_COUNT constant in scripts/check-event-names-sync.sh (in the evo-crm-community monorepo). Otherwise the sync job fails with DIVERGENT — lists match each other but count is N (expected M).
EvoFlow::BackfillContactEventsWorker ports historical Message(message_type: :activity) and ReportingEvent rows to evo-flow's ClickHouse via the /events/batch endpoint so the contact-events timeline (consumed by EvoFlow::PublishEventWorker's downstream surface) is populated for contacts that existed before the live publisher shipped.
⚠️ Do NOT run withDRY_RUN=falsein production until evo-flow'sIdempotencyServiceis deployed. Without consumer-side dedup, reruns duplicate events in ClickHouse (contact_eventsis a plainMergeTree). The rake task hard-stops onRails.env.production? && !DRY_RUN && CONFIRM != I_KNOW_WHAT_IM_DOINGfor this reason.
1. Dry-run (default — counts and logs a single sample payload, no POST):
bundle exec rake evo_flow:backfill # ALL records
bundle exec rake evo_flow:backfill DRY_RUN=true # same, explicitWatch the Sidekiq worker log for lines prefixed [EvoFlow][Backfill]:
would_backfill account=ALL type=message count=<n>(per source)sample_payload=...(exactly one, payload of the first item)
2. Inspect counts in ClickHouse (after a real publish):
SELECT count() FROM contact_events WHERE message_id LIKE 'backfill|%';The messageId is SHA256("backfill|<source>|<id>") and backfill| is the rollback selector.
3. Real publish (dev / staging):
DRY_RUN=false bundle exec rake evo_flow:backfill4. Real publish (production — requires CONFIRM):
DRY_RUN=false CONFIRM=I_KNOW_WHAT_IM_DOING bundle exec rake evo_flow:backfill5. Custom date window (default is 1.year.ago — matches the 365-day ClickHouse TTL on contact_events):
FROM_DATE=2026-04-01T00:00:00Z bundle exec rake evo_flow:backfillRollback (manual, on the evo-flow ClickHouse host):
ALTER TABLE contact_events DELETE
WHERE message_id LIKE 'backfill|%'
AND occurred_at >= toDateTime('2026-05-01 00:00:00');Known limitations:
- Events older than 365 days are not iterated (matches ClickHouse TTL).
- Notes are not portable (kept in the CRM panel — out of scope).
- Without the evo-flow
IdempotencyService, reruns duplicate events. The cursor survives crashes and is partitioned by thefrom_datewindow (backfill:cursor:<yyyy-mm-dd>:message/…:reporting_eventin Redis::Alfred), so changingFROM_DATEdoes not silently skip records.
Contributions are welcome! Please read CONTRIBUTING.md for guidelines on how to submit issues, propose features, and open pull requests.
Join our community to discuss ideas and collaborate.
For security issues, do not open a public issue. Email suporte@evofoundation.com.br or use GitHub's private vulnerability reporting. See SECURITY.md for details.
Evo CRM Backend is licensed under the Apache License 2.0. See LICENSE for details.
"Evolution Foundation", "Evolution" and "Evo CRM Backend" are trademarks of Evolution Foundation. See TRADEMARKS.md for the brand assets policy.
Third-party attributions are documented in NOTICE.
Made by Evolution Foundation · © 2026