chatwoot/chatwoot
Open-source live-chat, email support, omni-channel desk. An alternative to Intercom, Zendesk, Salesforce Service Cloud etc. 🔥💬
12 hidden assumptions · 6-stage pipeline · 10 components
Like any codebase, this fullstack makes assumptions it never checks — most are routine. The ones worth your attention are below.
Routes customer conversations across channels to agents with omnichannel inbox management
Customer initiates conversation through widget or channel, creating Contact and Conversation records. Messages flow through WebSocket to agent dashboard where ConversationStore updates UI state. Agents respond through API, triggering background jobs for delivery and real-time updates. Conversations are routed to inboxes, assigned to agents, and tracked through status changes.
Under the hood, the system uses 3 feedback loops, 4 data pools, 4 control points to manage its runtime behavior.
A 10-component fullstack. 2979 files analyzed. Data flows through 6 distinct pipeline stages.
Hidden Assumptions
Most of what this code assumes is routine. These 3 are the ones most likely to cause trouble here. The rest are minor; they're under "Show everything".
If server returns conversations with different schemas (missing properties, different structure), the merge operation will create conversations with undefined properties or overwrite important data, breaking real-time message display
If contact IDs are invalid, missing, or point to the same contact, ContactMergeAction will receive nil objects or attempt to merge a contact with itself, likely causing database constraint violations or data corruption
If database schema changes column names, API serialization changes field names, or internationalization affects attribute keys, contact filtering will silently fail to match any records
Show everything (9 more)
Assumes conversationsForADate array is already sorted by timestamp in ascending order for avatar display logic - compares current message with next message at index+1 to determine if sender changed
If this fails: If messages arrive out of order or array is not pre-sorted, avatar display logic will be incorrect, showing/hiding avatars at wrong positions and creating confusing conversation flows
app/javascript/widget/store/modules/conversation/helpers.js:groupConversationBySender
Assumes IndexedDB is available and functional, only checks for initialization failure but not for subsequent database corruption, quota exceeded, or browser security policies that disable IndexedDB
If this fails: Database operations after initialization can fail silently, causing cache reads to return empty arrays while cache keys indicate valid data exists, leading to unnecessary API calls and stale UI state
app/javascript/dashboard/api/CacheEnabledApiClient.js:getFromCache
Assumes cache keys from server API remain consistent between the initial cache_keys request and when cached data is actually used - no validation that cache hasn't been invalidated during the multi-step cache validation process
If this fails: Race condition where cache key validates as current but server data changes before cached data is returned, causing dashboard to display stale information until next cache refresh cycle
app/javascript/dashboard/api/CacheEnabledApiClient.js:validateCacheKey
City filter configuration has inconsistent dataType 'Number' (capitalized) while other location/text fields use 'text' or 'number' - assumes this dataType mismatch doesn't affect filter operator behavior or data validation
If this fails: City filters may not work correctly with text-based city names, causing type coercion errors or preventing users from filtering contacts by city location
app/javascript/dashboard/routes/dashboard/contacts/contactFilterItems/index.js:city
Assumes unlimited memory for storing all conversations in browser memory via allConversations array, with no upper limit or cleanup mechanism for long-running dashboard sessions
If this fails: In busy customer support environments, the conversation store will grow unbounded, eventually causing browser memory exhaustion, tab crashes, and degraded performance as conversation count increases
app/javascript/dashboard/store/modules/conversations/index.js:allConversations
Assumes authentication mechanism is mutually exclusive - if access token headers are present, user session authentication is bypassed entirely, with no validation that access token is actually valid before skipping session checks
If this fails: Malformed or invalid access tokens in headers will bypass session authentication, potentially allowing unauthenticated requests to proceed until validate_bot_access_token! is called, creating security gaps
app/controllers/api/base_controller.rb:authenticate_by_access_token?
Assumes label parameter is a single string value that can be safely URL-encoded with labels[]= format, but doesn't validate if label contains characters that break URL parameter parsing
If this fails: Contact labels containing special characters (=, &, %, spaces) will malform the query string, causing contact filtering by labels to return incorrect results or server parsing errors
app/javascript/dashboard/api/contacts.js:buildContactParams
Assumes system clock is accurate and Date.getTime() provides sufficient uniqueness for temporary message IDs when combined with getUuid(), but doesn't handle clock skew or rapid message creation scenarios
If this fails: If system clock jumps backward or multiple messages are created in rapid succession, timestamp-based logic for message ordering could fail, displaying messages out of sequence in widget conversations
app/javascript/widget/store/modules/conversation/helpers.js:createTemporaryMessage
Assumes store.dispatch('setUser') always resolves successfully and that getCurrentUser getter returns consistent user object structure with accounts array and account_id properties
If this fails: If user authentication fails or user object has unexpected structure, route navigation will redirect to wrong routes or cause JavaScript errors, potentially leaving users stuck on authentication pages
app/javascript/dashboard/routes/index.js:validateAuthenticateRoutePermission
Open the standalone hidden-assumptions report for chatwoot →
How Data Flows Through the System
Customer initiates conversation through widget or channel, creating Contact and Conversation records. Messages flow through WebSocket to agent dashboard where ConversationStore updates UI state. Agents respond through API, triggering background jobs for delivery and real-time updates. Conversations are routed to inboxes, assigned to agents, and tracked through status changes.
- Customer initiates conversation — Widget or channel webhook creates Contact and Conversation records, validates inbox settings and routing rules
- Message processing and validation — Incoming messages are validated, processed for content analysis, attached to conversation, and prepared for agent delivery [Message → Message]
- Real-time dashboard updates — ActionCable broadcasts message events to agent dashboard where ConversationStore mutations update Vue component state [Message → ConversationState] (config: default.channel_prefix)
- Agent response and routing — ConversationApi handles agent replies, status updates, and assignments through Rails controllers to appropriate delivery channels [ConversationState → Message]
- Contact management and search — ContactAPI processes contact filtering, merging, and search operations with pagination and label-based organization [Contact → ContactState]
- Cache and data persistence — CacheEnabledApiClient validates cache keys against server, uses IndexedDB for offline data access, and refreshes stale cache [API responses → Cached data]
Data Models
The data structures that flow between stages — the contracts that hold the system together.
app/models/conversation.rbActiveRecord model with id, account_id, inbox_id, status (open/resolved/pending), priority, assignee_id, contact_id, messages, labels, created_at, updated_at
Created when contact initiates conversation, assigned to agents, updated with messages and status changes, archived when resolved
app/models/contact.rbActiveRecord model with id, account_id, name, email, phone_number, identifier, custom_attributes hash, created_at, last_activity_at
Created on first interaction, updated with each message or agent action, can be merged with duplicates
app/models/message.rbActiveRecord model with id, conversation_id, content, message_type (incoming/outgoing), sender_type, sender_id, attachments, created_at, content_attributes
Created when customer or agent sends message, processed for content analysis, delivered via WebSocket to dashboard
app/models/inbox.rbActiveRecord model with id, account_id, name, channel_type (web_widget, email, api, etc), settings hash, greeting_enabled, working_hours
Created to connect communication channel, configured with routing rules, used to route incoming conversations to appropriate agents
app/javascript/dashboard/store/modules/conversations/index.jsVuex state with allConversations array, selectedChatId, chatStatusFilter, appliedFilters array, currentInbox, attachments object
Loaded on dashboard init, updated via WebSocket events, filtered and sorted by user actions
app/javascript/dashboard/store/modules/contacts/index.jsVuex state with records object (contacts by id), meta (count, currentPage, hasMore), uiFlags (loading states), appliedFilters array
Populated on contact list load, updated when contacts are created/modified, filtered by search and label criteria
System Behavior
How the system operates at runtime — where data accumulates, what loops, what waits, and what controls what.
Data Pools
Accumulates all active conversations with messages, status, and agent assignments for dashboard display
Stores API responses locally with cache keys for offline access and reduced server load
Persists all conversation, contact, message, and user data with relationships and constraints
Broadcasts real-time updates to connected agent dashboards via Redis-backed WebSocket channels
Feedback Loops
- Conversation Assignment Loop (auto-scale, balancing) — Trigger: New conversation created without assignee. Action: System checks agent availability, workload, and inbox routing rules to assign conversation. Exit: Conversation assigned to available agent or remains unassigned.
- Cache Invalidation Loop (cache-invalidation, balancing) — Trigger: Server cache key changes detected by CacheEnabledApiClient. Action: Local IndexedDB cache cleared and fresh data fetched from API. Exit: Cache key matches server version.
- WebSocket Reconnection Loop (circuit-breaker, reinforcing) — Trigger: ActionCable connection lost. Action: Dashboard attempts reconnection with exponential backoff. Exit: WebSocket connection restored or user refreshes page.
Delays
- Background Job Processing (async-processing, ~variable) — Email notifications, webhook deliveries, and data cleanup happen asynchronously
- IndexedDB Cache Lookup (cache-ttl, ~~50ms) — Dashboard loads cached data before validating freshness with server
- WebSocket Message Propagation (eventual-consistency, ~~100ms) — Agent dashboard updates appear shortly after message events
Control Points
- Redis Configuration (env-var) — Controls: WebSocket channel prefix, Redis connection settings, and SSL verification mode. Default: REDIS_URL, REDIS_PASSWORD env vars
- API Authentication Mode (runtime-toggle) — Controls: Whether requests authenticate via access token headers or user sessions. Default: null
- Cache Model Names (architecture-switch) — Controls: Which data types use IndexedDB caching vs always fetch from network. Default: inbox model enabled
- Channel Integrations (feature-flag) — Controls: Which communication channels are available for inbox creation. Default: webhooks, dashboard_apps, and other integrations configured
Technology Stack
Backend API framework handling conversations, contacts, and user management
Frontend framework for agent dashboard with reactive conversation and contact interfaces
State management for dashboard data with modules for conversations, contacts, and UI state
WebSocket implementation for real-time conversation updates to agent dashboards
Backs ActionCable channels and provides caching layer for conversation data
Primary database storing conversations, contacts, messages, and user accounts
Client-side storage for caching API responses to reduce server load and enable offline access
Authorization framework controlling access to conversations and account resources
Frontend build tool for Vue.js dashboard and embeddable widget compilation
Background job processing for email delivery, webhooks, and async data operations
Key Components
- Api::BaseController (gateway) — Authenticates API requests using access tokens or user sessions, enforces authorization policies via Pundit
app/controllers/api/base_controller.rb - ContactMergeAction (processor) — Merges duplicate contacts by combining their conversations, custom attributes, and contact data
app/controllers/api/v1/accounts/actions/contact_merges_controller.rb - AgentBuilder (factory) — Creates new agent users with proper account associations, roles, and invitation emails
app/controllers/api/v1/accounts/agents_controller.rb - ConversationApi (adapter) — Handles all conversation-related API calls including filtering, status changes, assignments, and message operations
app/javascript/dashboard/api/inbox/conversation.js - ContactAPI (adapter) — Manages contact CRUD operations, search, filtering, label management, and conversation initiation
app/javascript/dashboard/api/contacts.js - CacheEnabledApiClient (adapter) — Provides IndexedDB caching layer for API responses with cache key validation to reduce server requests
app/javascript/dashboard/api/CacheEnabledApiClient.js - ConversationStore (store) — Manages conversation state in Vue dashboard including message updates, status changes, and real-time sync
app/javascript/dashboard/store/modules/conversations/index.js - RouterValidation (gateway) — Validates user authentication and account access before allowing navigation to dashboard routes
app/javascript/dashboard/routes/index.js - ConversationHelpers (processor) — Processes widget conversation messages for display grouping, avatar logic, and temporary message handling
app/javascript/widget/store/modules/conversation/helpers.js - DataManager (store) — Manages IndexedDB operations for caching dashboard data with cache key validation and data replacement
app/javascript/dashboard/helper/CacheHelper/DataManager.js
Explore the interactive analysis
See the full architecture map, data flow, and code patterns visualization.
Analyze on CodeSeaRelated Fullstack Repositories
Frequently Asked Questions
What is chatwoot used for?
Routes customer conversations across channels to agents with omnichannel inbox management chatwoot/chatwoot is a 10-component fullstack written in Ruby. Data flows through 6 distinct pipeline stages. The codebase contains 2979 files.
How is chatwoot architected?
chatwoot is organized into 6 architecture layers: API Controllers, Service Objects, Models, Background Jobs, and 2 more. Data flows through 6 distinct pipeline stages. This layered structure keeps concerns separated and modules independent.
How does data flow through chatwoot?
Data moves through 6 stages: Customer initiates conversation → Message processing and validation → Real-time dashboard updates → Agent response and routing → Contact management and search → .... Customer initiates conversation through widget or channel, creating Contact and Conversation records. Messages flow through WebSocket to agent dashboard where ConversationStore updates UI state. Agents respond through API, triggering background jobs for delivery and real-time updates. Conversations are routed to inboxes, assigned to agents, and tracked through status changes. This pipeline design reflects a complex multi-stage processing system.
What technologies does chatwoot use?
The core stack includes Ruby on Rails (Backend API framework handling conversations, contacts, and user management), Vue.js (Frontend framework for agent dashboard with reactive conversation and contact interfaces), Vuex (State management for dashboard data with modules for conversations, contacts, and UI state), ActionCable (WebSocket implementation for real-time conversation updates to agent dashboards), Redis (Backs ActionCable channels and provides caching layer for conversation data), PostgreSQL (Primary database storing conversations, contacts, messages, and user accounts), and 4 more. This broad technology surface reflects a mature project with many integration points.
What system dynamics does chatwoot have?
chatwoot exhibits 4 data pools (ConversationStore, IndexedDB Cache), 3 feedback loops, 4 control points, 3 delays. The feedback loops handle auto-scale and cache-invalidation. These runtime behaviors shape how the system responds to load, failures, and configuration changes.
What design patterns does chatwoot use?
5 design patterns detected: Service Object Pattern, API Client Abstraction, Vuex Store Modules, Real-time Event Broadcasting, Multi-layer Caching.
Analyzed on April 20, 2026 by CodeSea. Written by Karolina Sarna.