# Pull Request Template
## Description
Please include a summary of the change and issue(s) fixed. Also, mention
relevant motivation, context, and any dependencies that this change
requires.
Fixes:
The LLM call was wrapped in a transaction. This is an anti-pattern and
caused idle-connections which PG eventually terminated with
`PQconsumeInput() FATAL: terminating connection due to
idle-in-transaction timeout`
This resulted in activity messages being missing in some conversations
on captain handoff, failures queueing up for retry and captain
responding long after conversation was marked open/snoozed.
## Type of change
- [x] Bug fix (non-breaking change which fixes an issue)
## How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide
instructions so we can reproduce. Please also list any relevant details
for your test configuration.
locally and specs
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules
---------
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
# Pull Request Template
## Description
## Type of change
typo fix
## How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide
instructions so we can reproduce. Please also list any relevant details
for your test configuration.
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules
Langfuse logging improvements
## Description
Please include a summary of the change and issue(s) fixed. Also, mention
relevant motivation, context, and any dependencies that this change
requires.
Fixes # (issue)
For reply suggestion: the errors are being stored inside output field,
but observations should be marked as errors.
For assistant: add credit_used metadata to filter handoffs from
ai-replies
For langfuse tool call: add `observation_type=tool`
## Type of change
- [x] Bug fix (non-breaking change which fixes an issue)
## How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide
instructions so we can reproduce. Please also list any relevant details
for your test configuration.
before:
<img width="1028" height="57" alt="image"
src="https://github.com/user-attachments/assets/70f6a36e-6c33-444c-a083-723c7c9e823a"
/>
after:
<img width="872" height="69" alt="image"
src="https://github.com/user-attachments/assets/1b6b6f5f-5384-4e9c-92ba-f56748fec6dd"
/>
`credit_used` to filter handoffs from AI replies that cause credit usage
<img width="1082" height="672" alt="image"
src="https://github.com/user-attachments/assets/90914227-553a-4c03-bc43-56b2018ac7c1"
/>
set `observation_type` to `tool`
<img width="726" height="1452" alt="image"
src="https://github.com/user-attachments/assets/e639cc9b-1c6c-4427-887e-23e5523bf64f"
/>
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules
# Pull Request Template
## Description
Instruments captain v2
## Type of change
- [x] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide
instructions so we can reproduce. Please also list any relevant details
for your test configuration.
Local testing:
<img width="864" height="510" alt="image"
src="https://github.com/user-attachments/assets/855ebce5-e8b8-4d22-b0bb-0d413769a6ab"
/>
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules
---------
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
## Summary
This PR reduces duplicate failure noise for audio transcription jobs
that fail with permanent HTTP 400 responses, and fixes a file-format
edge case causing intermittent 400s.
Sentry issue: [CHATWOOT-99E /
6660541334](https://chatwoot-p3.sentry.io/issues/6660541334/)
## Confirmed root cause
For some attachments, the stored filename had no extension (example:
`speech`, content type `audio/mpeg`).
When the temporary transcription upload file was created without an
extension, OpenAI returned:
`Unrecognized file format` (HTTP 400).
## Scope of changes
1. `Messages::AudioTranscriptionJob`
- Keeps `discard_on Faraday::BadRequestError` to avoid retry storms on
permanent request errors.
- Adds explicit Rails warning logs for discarded jobs with
attachment/job/status context.
2. `Messages::AudioTranscriptionService`
- Keeps guaranteed temp file cleanup via `ensure`.
- Ensures temp upload files include an extension when the original
filename has none, derived from blob `content_type`.
- This addresses intermittent failures like extensionless `audio/mpeg`
files.
## Reproduction
Enable audio transcription for an account and process an audio
attachment whose stored filename has no extension (for example `speech`)
but valid audio content type (`audio/mpeg`).
Before this fix, OpenAI transcription could return HTTP 400
`Unrecognized file format` for that attachment while similar attachments
with extensions succeeded.
## Testing
Ran:
`bundle exec rubocop
enterprise/app/jobs/messages/audio_transcription_job.rb
enterprise/app/services/messages/audio_transcription_service.rb`
Result: both modified files pass lint with no offenses.
## Description
Fixes a critical bug where conversations assigned to a team could be
auto-assigned to agents outside that team when all team members were at
capacity.
## Type of change
- [ ] Bug fix (non-breaking change which fixes an issue)
## Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Changes core assignment selection for both legacy and v2 flows;
misconfiguration of `allow_auto_assign` or team membership could cause
conversations to remain unassigned.
>
> **Overview**
> Prevents auto-assignment from crossing team boundaries by filtering
eligible agents to the conversation’s `team` members (and requiring
`team.allow_auto_assign`) in both the legacy `AutoAssignmentHandler`
path and the v2 `AutoAssignment::AssignmentService` (including the
Enterprise override).
>
> Adds test coverage to ensure team-scoped conversations only assign to
team members, and are skipped when team auto-assign is disabled or no
team members are available; also updates the conversations controller
spec setup to include team membership.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
67ed2bda0cd8ffd56c7e0253b86369dead2e6155. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
## Linear Ticket:
https://linear.app/chatwoot/issue/CW-6081/review-feedback
## Description
Assignment V2 Service Enhancements
- Enable Assignment V2 on plan upgrade
- Fix UI issue with fair distribution policy display
- Add advanced assignment feature flag and enhance Assignment V2
capabilities
## Type of change
- [ ] Bug fix (non-breaking change which fixes an issue)
## How Has This Been Tested?
This has been tested using the UI.
## Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Changes auto-assignment execution paths, rate limiting defaults, and
feature-flag gating (including premium plan behavior), which could
affect which conversations get assigned and when. UI rewires inbox
settings and policy flows, so regressions are possible around
navigation/linking and feature visibility.
>
> **Overview**
> **Adds a new premium `advanced_assignment` feature flag** and uses it
to gate capacity/balanced assignment features in the UI (sidebar entry,
settings routes, assignment-policy landing cards) and backend
(Enterprise balanced selector + capacity filtering).
`advanced_assignment` is marked premium, included in Business plan
entitlements, and auto-synced in Enterprise accounts when
`assignment_v2` is toggled.
>
> **Improves Assignment V2 policy UX** by adding an inbox-level
“Conversation Assignment” section (behind `assignment_v2`) that can
link/unlink an assignment policy, navigate to create/edit policy flows
with `inboxId` query context, and show an inbox-link prompt after
creating a policy. The policy form now defaults to enabled, disables the
`balanced` option with a premium badge/message when unavailable, and
inbox lists support click-to-navigate.
>
> **Tightens/adjusts auto-assignment behavior**: bulk assignment now
requires `inbox.enable_auto_assignment?`, conversation ordering uses the
attached `assignment_policy` priority, and rate limiting uses
`assignment_policy` config with an infinite default limit while still
tracking assignments. Tests and i18n strings are updated accordingly.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
23bc03bf75ee4376071e4d7fc7cd564c601d33d7. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Pranav <pranav@chatwoot.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
# Pull Request Template
## Description
Reply suggestions uses `search_documentation`. While this is useful,
there is a subtle bug, a user's message may be in a different language
(say spanish) than the FAQs present (english).
This results in embedding search in spanish and compared against english
vectors, which results in poor retrieval and poor suggestions.
Fixes # (issue)
This PR fixes the above behaviour by making a small llm call translate
the query before searching in the search documentation tool
## Type of change
- [x] Bug fix (non-breaking change which fixes an issue)
## How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide
instructions so we can reproduce. Please also list any relevant details
for your test configuration.
before:
<img width="894" height="157" alt="image"
src="https://github.com/user-attachments/assets/83871ee5-511e-4432-8b99-39e803759f63"
/>
after:
<img width="1149" height="294" alt="image"
src="https://github.com/user-attachments/assets/f9617d7a-6d48-4ca1-ad1c-2181e16c1f3d"
/>
test on rails console:
<img width="2094" height="380" alt="image"
src="https://github.com/user-attachments/assets/159fdaa5-8808-49d2-be5d-304d69fa97f7"
/>
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules
## Bug Explanation
- The Super Admin limits form renders inputs by iterating the keys of
`account.limits`.
- When `account.limits` was present, `AccountLimitsField#to_s` returned
only that hash (no defaults).
- On save, `SuperAdmin::AccountsController` compacts the limits hash,
removing blank keys.
- Result: if only one key (e.g., `agents`) was saved, the other keys
were missing from the hash and their fields disappeared on the next
render.
## Fix
- Always start from a defaults hash of all expected limit keys and merge
in any saved overrides.
- This keeps the UI stable and ensures all limit inputs remain visible
even when the stored hash is partial.
- Upgraded meta_request to `0.8.5` to stop a dev‑only `SystemStackError`
caused by JSON‑encoding ActiveRecord::Transaction in Rails 7.2. No
production behavior changes.
## Reproduction Steps
1. In Super Admin, edit an account and set only `agents` in the limits;
leave other limit fields blank and save.
2. Re-open the same account in Super Admin.
3. Observe that only `agents` is rendered and other limit fields are
missing.
## Testing
- Tested on UI
---------
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
# Pull Request Template
## Description
Fixes # (issue)
When we migrated to RubyLLM, images weren't being sent properly in
RubyLLM format to the model, so it did not understand images.
## Type of change
Please delete options that are not relevant.
- [x] Bug fix (non-breaking change which fixes an issue)
## How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide
instructions so we can reproduce. Please also list any relevant details
for your test configuration.
specs + local testing
Current behaviour on staging:
<img width="772" height="1012" alt="image"
src="https://github.com/user-attachments/assets/7b7d360f-dea4-48af-b20b-ee4c98a38a85"
/>
local testing with fix:
<img width="792" height="1216" alt="image"
src="https://github.com/user-attachments/assets/5ef82452-015e-4bda-a68f-884d00acb014"
/>
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules
---------
Co-authored-by: Sojan Jose <sojan@pepalo.com>
We are expanding Chatwoot’s automation capabilities by
introducing **Conversation Workflows**, a dedicated section in settings
where teams can configure rules that govern how conversations are closed
and what information agents must fill before resolving. This feature
helps teams enforce data consistency, collect structured resolution
information, and ensure downstream reporting is accurate.
Instead of having auto‑resolution buried inside Account Settings, we
introduced a new sidebar item:
- Auto‑resolve conversations (existing behaviour)
- Required attributes on resolution (new)
This groups all conversation‑closing logic into a single place.
#### Required Attributes on Resolve
Admins can now pick which custom conversation attributes must be filled
before an agent can resolve a conversation.
**How it works**
- Admin selects one or more attributes from the list of existing
conversation level custom attributes.
- These selected attributes become mandatory during resolution.
- List all the attributes configured via Required Attributes (Text,
Number, Link, Date, List, Checkbox)
- When an agent clicks Resolve Conversation:
If attributes already have values → the conversation resolves normally.
If attributes are missing → a modal appears prompting the agent to fill
them.
<img width="1554" height="1282" alt="CleanShot 2025-12-10 at 11 42
23@2x"
src="https://github.com/user-attachments/assets/4cd5d6e1-abe8-4999-accd-d4a08913b373"
/>
#### Custom Attributes Integration
On the Custom Attributes page, we will surfaced indicators showing how
each attribute is being used.
Each attribute will show badges such as:
- Resolution → used in the required‑on‑resolve workflow
- Pre‑chat form → already existing
<img width="2390" height="1822" alt="CleanShot 2025-12-10 at 11 43
42@2x"
src="https://github.com/user-attachments/assets/b92a6eb7-7f6c-40e6-bf23-6a5310f2d9c5"
/>
#### Admin Flow
- Navigate to Settings → Conversation Workflows.
- Under Required attributes on resolve, click Add Required Attribute.
- Pick from the dropdown list of conversation attributes.
- Save changes.
Agents will now be prompted automatically whenever they resolve.
<img width="2434" height="872" alt="CleanShot 2025-12-10 at 11 44 42@2x"
src="https://github.com/user-attachments/assets/632fc0e5-767c-4a1c-8cf4-ffe3d058d319"
/>
#### NOTES
- The Required Attributes on Resolve modal should only appear when
values are missing.
- Required attributes must block the resolution action until satisfied.
- Bulk‑resolve actions should follow the same rules — any conversation
missing attributes cannot be bulk‑resolved, rest will be resolved, show
a notification that the resolution cannot be done.
- API resolution does not respect the attributes.
---------
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
CSAT scores are helpful, but on their own they rarely tell the full
story. A drop in rating can come from delayed timelines, unclear
expectations, or simple misunderstandings, even when the issue itself
was handled correctly.
Review Notes for CSAT let admins/report manager roles add internal-only
context next to each CSAT response. This makes it easier to interpret
scores properly and focus on patterns and root causes, not just numbers.
<img width="2170" height="1680" alt="image"
src="https://github.com/user-attachments/assets/56df7fab-d0a7-4a94-95b9-e4c459ad33d5"
/>
### Why this matters
* Capture the real context behind individual CSAT ratings
* Clarify whether a low score points to a genuine service issue or a
process gap
* Spot recurring themes across conversations and teams
* Make CSAT reviews more useful for leadership reviews and
retrospectives
### How Review Notes work
**View CSAT responses**
Open the CSAT report to see overall metrics, rating distribution, and
individual responses.
**Add a Review Note**
For any CSAT entry, managers can add a Review Note directly below the
customer’s feedback.
**Document internal insights**
Use Review Notes to capture things like:
* Why a score was lower or higher than expected
* Patterns you are seeing across similar cases
* Observations around communication, timelines, or customer expectations
Review Notes are visible only to administrators and people with report
access only. We may expand visibility to agents in the future based on
feedback. However, customers never see them.
Each note clearly shows who added it and when, making it easy to review
context and changes over time.
## Description
Adds the ability to sort companies by the number of contacts they have
(contacts_count) in ascending or descending order. This is part of the
Chatwoot 5.0 release requirements for the companies feature.
The implementation uses a scope-based approach consistent with other
sorting implementations in the codebase (e.g., contacts sorting by
last_activity_at).
## Type of change
- [x] New feature (non-breaking change which adds functionality)
## Available Sorting Options
After this change, the Companies API supports the following sorting
options:
| Sort Field | Type | Ascending | Descending |
|------------|------|-----------|------------|
| `name` | string | `?sort=name` | `?sort=-name` |
| `domain` | string | `?sort=domain` | `?sort=-domain` |
| `created_at` | datetime | `?sort=created_at` | `?sort=-created_at` |
| `contacts_count` | integer (scope) | `?sort=contacts_count` |
`?sort=-contacts_count` |
**Note:** Prefix with `-` for descending order. Companies with NULL
contacts_count will appear last (NULLS LAST).
## CURL Examples
**Sort by contacts count (ascending):**
```bash
curl -X GET 'https://app.chatwoot.com/api/v1/accounts/{account_id}/companies?sort=contacts_count' \
-H 'api_access_token: YOUR_API_TOKEN'
```
**Sort by contacts count (descending):**
```bash
curl -X GET 'https://app.chatwoot.com/api/v1/accounts/{account_id}/companies?sort=-contacts_count' \
-H 'api_access_token: YOUR_API_TOKEN'
```
**Sort by name (ascending):**
```bash
curl -X GET 'https://app.chatwoot.com/api/v1/accounts/{account_id}/companies?sort=name' \
-H 'api_access_token: YOUR_API_TOKEN'
```
**Sort by created_at (descending):**
```bash
curl -X GET 'https://app.chatwoot.com/api/v1/accounts/{account_id}/companies?sort=-created_at' \
-H 'api_access_token: YOUR_API_TOKEN'
```
**With pagination:**
```bash
curl -X GET 'https://app.chatwoot.com/api/v1/accounts/{account_id}/companies?sort=-contacts_count&page=2' \
-H 'api_access_token: YOUR_API_TOKEN'
```
## How Has This Been Tested?
- Added RSpec tests for both ascending and descending sort
- All 24 existing specs pass
- Manually tested the sorting functionality with test data
**Test configuration:**
- Ruby 3.4.4
- Rails 7.1.5.2
- PostgreSQL (test database)
**To reproduce:**
1. Run `bundle exec rspec
spec/enterprise/controllers/api/v1/accounts/companies_controller_spec.rb`
2. All tests should pass (24 examples, 0 failures)
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
## Technical Details
**Backend changes:**
- Controller: Added `sort_on :contacts_count` with scope-based sorting
- Model: Added `order_on_contacts_count` scope using
`Arel::Nodes::SqlLiteral` and `sanitize_sql_for_order` with `NULLS LAST`
for consistent NULL handling
- Specs: Added 2 new tests for ascending/descending sort validation
**Files changed:**
- `enterprise/app/controllers/api/v1/accounts/companies_controller.rb`
- `enterprise/app/models/company.rb`
-
`spec/enterprise/controllers/api/v1/accounts/companies_controller_spec.rb`
**Note:** This PR only includes the backend implementation. Frontend
changes (sort menu UI + i18n) will follow in a separate commit.
---------
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
Migrates our analytics integration on Cloud from PostHog to Amplitude.
This change updates the core AnalyticsHelper class to use the Amplitude
SDK while maintaining the same tracking interface. Rest of all existing
analytics calls throughout the codebase continue to work without
modification.
**Changes:**
- Replace PostHog analytics with Amplitude SDK
- Rename ANALYTICS_TOKEN to CLOUD_ANALYTICS_TOKEN for clarity
- Fix bug in page() method signature that was causing malformed payloads
## Description
Implements comprehensive search functionality with advanced filtering
capabilities for Chatwoot (Linear: CW-5956).
This PR adds:
1. **Time-based filtering** for contacts and conversations (SQL-based
search)
2. **Advanced message search** with multiple filters
(OpenSearch/Elasticsearch-based)
- **`from` filter**: Filter messages by sender (format: `contact:42` or
`agent:5`)
- **`inbox_id` filter**: Filter messages by specific inbox
- **Time range filters**: Filter messages using `since` and `until`
parameters (Unix timestamps in seconds)
- **90-day limit enforcement**: Automatically limits searches to the
last 90 days to prevent performance issues
The implementation extends the existing `Enterprise::SearchService`
module for advanced features and adds time filtering to the base
`SearchService` for SQL-based searches.
## API Documentation
### Base URL
All search endpoints follow this pattern:
```
GET /api/v1/accounts/{account_id}/search/{resource}
```
### Authentication
All requests require authentication headers:
```
api_access_token: YOUR_ACCESS_TOKEN
```
---
## 1. Search All Resources
**Endpoint:** `GET /api/v1/accounts/{account_id}/search`
Returns results from all searchable resources (contacts, conversations,
messages, articles).
### Parameters
| Parameter | Type | Description | Required |
|-----------|------|-------------|----------|
| `q` | string | Search query | Yes |
| `page` | integer | Page number (15 items per page) | No |
| `since` | integer | Unix timestamp (contacts/conversations only) | No
|
| `until` | integer | Unix timestamp (contacts/conversations only) | No
|
### Example Request
```bash
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search?q=customer" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
### Example Response
```json
{
"payload": {
"contacts": [...],
"conversations": [...],
"messages": [...],
"articles": [...]
}
}
```
---
## 2. Search Contacts
**Endpoint:** `GET /api/v1/accounts/{account_id}/search/contacts`
Search contacts by name, email, phone number, or identifier with
optional time filtering.
### Parameters
| Parameter | Type | Description | Required |
|-----------|------|-------------|----------|
| `q` | string | Search query | Yes |
| `page` | integer | Page number (15 items per page) | No |
| `since` | integer | Unix timestamp - filter by last_activity_at | No |
| `until` | integer | Unix timestamp - filter by last_activity_at | No |
### Example Requests
**Basic search:**
```bash
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/contacts?q=john" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
**Search contacts active in the last 7 days:**
```bash
SINCE=$(date -v-7d +%s)
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/contacts?q=john&since=${SINCE}" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
**Search contacts active between 30 and 7 days ago:**
```bash
SINCE=$(date -v-30d +%s)
UNTIL=$(date -v-7d +%s)
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/contacts?q=john&since=${SINCE}&until=${UNTIL}" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
### Example Response
```json
{
"payload": {
"contacts": [
{
"id": 42,
"email": "john@example.com",
"name": "John Doe",
"phone_number": "+1234567890",
"identifier": "user_123",
"additional_attributes": {},
"created_at": 1701234567
}
]
}
}
```
---
## 3. Search Conversations
**Endpoint:** `GET /api/v1/accounts/{account_id}/search/conversations`
Search conversations by display ID, contact name, email, phone number,
or identifier with optional time filtering.
### Parameters
| Parameter | Type | Description | Required |
|-----------|------|-------------|----------|
| `q` | string | Search query | Yes |
| `page` | integer | Page number (15 items per page) | No |
| `since` | integer | Unix timestamp - filter by last_activity_at | No |
| `until` | integer | Unix timestamp - filter by last_activity_at | No |
### Example Requests
**Basic search:**
```bash
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/conversations?q=billing" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
**Search conversations active in the last 24 hours:**
```bash
SINCE=$(date -v-1d +%s)
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/conversations?q=billing&since=${SINCE}" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
**Search conversations from last month:**
```bash
SINCE=$(date -v-30d +%s)
UNTIL=$(date +%s)
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/conversations?q=billing&since=${SINCE}&until=${UNTIL}" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
### Example Response
```json
{
"payload": {
"conversations": [
{
"id": 123,
"display_id": 45,
"inbox_id": 1,
"status": "open",
"messages": [...],
"meta": {...}
}
]
}
}
```
---
## 4. Search Messages (Advanced)
**Endpoint:** `GET /api/v1/accounts/{account_id}/search/messages`
Advanced message search with multiple filters powered by
OpenSearch/Elasticsearch.
### Prerequisites
- OpenSearch/Elasticsearch must be running (`OPENSEARCH_URL` env var
configured)
- Account must have `advanced_search` feature flag enabled
- Messages must be indexed in OpenSearch
### Parameters
| Parameter | Type | Description | Required |
|-----------|------|-------------|----------|
| `q` | string | Search query | Yes |
| `page` | integer | Page number (15 items per page) | No |
| `from` | string | Filter by sender: `contact:{id}` or `agent:{id}` |
No |
| `inbox_id` | integer | Filter by specific inbox ID | No |
| `since` | integer | Unix timestamp - searches from this time (max 90
days ago) | No |
| `until` | integer | Unix timestamp - searches until this time | No |
### Important Notes
- **90-Day Limit**: If `since` is not provided, searches default to the
last 90 days
- If `since` exceeds 90 days, returns `422` error: "Search is limited to
the last 90 days"
- All time filters use message `created_at` timestamp
### Example Requests
**Basic message search:**
```bash
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/messages?q=refund" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
**Search messages from a specific contact:**
```bash
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/messages?q=refund&from=contact:42" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
**Search messages from a specific agent:**
```bash
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/messages?q=refund&from=agent:5" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
**Search messages in a specific inbox:**
```bash
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/messages?q=refund&inbox_id=3" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
**Search messages from the last 7 days:**
```bash
SINCE=$(date -v-7d +%s)
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/messages?q=refund&since=${SINCE}" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
**Search messages between specific dates:**
```bash
SINCE=$(date -v-30d +%s)
UNTIL=$(date -v-7d +%s)
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/messages?q=refund&since=${SINCE}&until=${UNTIL}" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
**Combine all filters:**
```bash
SINCE=$(date -v-14d +%s)
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/messages?q=refund&from=contact:42&inbox_id=3&since=${SINCE}" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
**Attempt to search beyond 90 days (returns error):**
```bash
SINCE=$(date -v-120d +%s)
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/messages?q=refund&since=${SINCE}" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
### Example Response (Success)
```json
{
"payload": {
"messages": [
{
"id": 789,
"content": "I need a refund for my purchase",
"message_type": "incoming",
"created_at": 1701234567,
"conversation_id": 123,
"inbox_id": 3,
"sender": {
"id": 42,
"type": "contact"
}
}
]
}
}
```
### Example Response (90-day limit exceeded)
```json
{
"error": "Search is limited to the last 90 days"
}
```
**Status Code:** `422 Unprocessable Entity`
---
## 5. Search Articles
**Endpoint:** `GET /api/v1/accounts/{account_id}/search/articles`
Search help center articles by title or content.
### Parameters
| Parameter | Type | Description | Required |
|-----------|------|-------------|----------|
| `q` | string | Search query | Yes |
| `page` | integer | Page number (15 items per page) | No |
### Example Request
```bash
curl -X GET "https://app.chatwoot.com/api/v1/accounts/1/search/articles?q=installation" \
-H "api_access_token: YOUR_ACCESS_TOKEN"
```
### Example Response
```json
{
"payload": {
"articles": [
{
"id": 456,
"title": "Installation Guide",
"slug": "installation-guide",
"portal_slug": "help",
"account_id": 1,
"category_name": "Getting Started",
"status": "published",
"updated_at": 1701234567
}
]
}
}
```
---
## Technical Implementation
### SQL-Based Search (Contacts, Conversations, Articles)
- Uses PostgreSQL `ILIKE` queries by default
- Optional GIN index support via `search_with_gin` feature flag for
better performance
- Time filtering uses `last_activity_at` for contacts/conversations
- Returns paginated results (15 per page)
### Advanced Search (Messages)
- Powered by OpenSearch/Elasticsearch via Searchkick gem
- Requires `OPENSEARCH_URL` environment variable
- Requires `advanced_search` account feature flag
- Enforces 90-day lookback limit via
`Limits::MESSAGE_SEARCH_TIME_RANGE_LIMIT_DAYS`
- Validates inbox access permissions before filtering
- Returns paginated results (15 per page)
---
## Type of change
- [x] New feature (non-breaking change which adds functionality)
- [x] Enhancement (improves existing functionality)
---
## How Has This Been Tested?
### Unit Tests
- **Contact Search Tests**: 3 new test cases for time filtering
(`since`, `until`, combined)
- **Conversation Search Tests**: 3 new test cases for time filtering
- **Message Search Tests**: 10+ test cases covering:
- Individual filters (`from`, `inbox_id`, time range)
- Combined filters
- Permission validation for inbox access
- Feature flag checks
- 90-day limit enforcement
- Error handling for exceeded time limits
### Test Commands
```bash
# Run all search controller tests
bundle exec rspec spec/controllers/api/v1/accounts/search_controller_spec.rb
# Run search service tests (includes enterprise specs)
bundle exec rspec spec/services/search_service_spec.rb
```
### Manual Testing Setup
A rake task is provided to create 50,000 test messages across multiple
inboxes:
```bash
# 1. Create test data
bundle exec rake search:setup_test_data
# 2. Start OpenSearch
mise elasticsearch-start
# 3. Reindex messages
rails runner "Message.search_index.import Message.all"
# 4. Enable feature flag
rails runner "Account.first.enable_features('advanced_search')"
# 5. Test via API or Rails console
```
---
## Checklist
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [x] I have made corresponding changes to the documentation (this PR
description)
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
---
## Additional Notes
### Requirements
- **OpenSearch/Elasticsearch**: Required for advanced message search
- Set `OPENSEARCH_URL` environment variable
- Example: `export OPENSEARCH_URL=http://localhost:9200`
- **Feature Flags**:
- `advanced_search`: Account-level flag for message advanced search
- `search_with_gin` (optional): Account-level flag for GIN-based SQL
search
### Performance Considerations
- 90-day limit prevents expensive long-range queries on large datasets
- GIN indexes recommended for high-volume search on SQL-based resources
- OpenSearch/Elasticsearch provides faster full-text search for messages
### Breaking Changes
- None. All new parameters are optional and backward compatible.
### Frontend Integration
- Frontend PR tracking advanced search UI will consume these endpoints
- Time range pickers should convert JavaScript `Date` to Unix timestamps
(seconds)
- Date conversion: `Math.floor(date.getTime() / 1000)`
### Error Handling
- Invalid `from` parameter format is silently ignored (filter not
applied)
- Time range exceeding 90 days returns `422` with error message
- Missing `q` parameter returns `422` (existing behavior)
- Unauthorized inbox access is filtered out (no error, just excluded
from results)
---------
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This PR fixes the HTTP 500 timeout errors occurring when deleting SLA
policies that have large volumes of historical data.
The fix moves the deletion workflow to asynchronous background
processing using the existing `DeleteObjectJob`.
By offloading heavy cascaded deletions (applied SLAs, SLA events,
conversation nullifications) from the request cycle, the API can now
return immediately while the cleanup continues in the background
avoiding the `Rack::Timeout::RequestTimeoutException`. This ensures that
SLA policies can be deleted reliably, regardless of data size.
### Problem
Deleting an SLA policy via `DELETE
/api/v1/accounts/{account_id}/sla_policies/{id}` fails consistently with
`Rack::Timeout::RequestTimeoutException (15s)` for policies with large
amounts of related data.
Because the current implementation performs all dependent deletions
**synchronously**, Rails processes:
- `has_many :applied_slas, dependent: :destroy` (thousands)
- Each `AppliedSla#destroy` → triggers destruction of many `SlaEvent`
records
- `has_many :conversations, dependent: :nullify` (thousands)
This processing far exceeds the Rack timeout window and consistently
triggers HTTP 500 errors for users.
### Solution
This PR applies the same pattern used successfully in Inbox deletion.
**Move deletion to async background jobs**
- Uses `DeleteObjectJob` for centralized, reliable cleanup.
- Allows the DELETE API call to respond immediately.
**Chunk large datasets**
- Records are processed in **batches of 5,000** to reduce DB load and
avoid job timeouts.
## Description
- Replaces Stripe Checkout session flow with direct card charging for AI
credit top-ups
- Adds a two-step confirmation modal (select package → confirm purchase)
for better UX
- Creates Stripe invoice directly and charges the customer's default
payment method immediately
## Type of change
- [ ] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
- Using the specs
- UI manual test cases
<img width="945" height="580" alt="image"
src="https://github.com/user-attachments/assets/52bdad46-cd0e-4927-b13f-54c6b6353bcc"
/>
<img width="945" height="580" alt="image"
src="https://github.com/user-attachments/assets/231bc7e9-41ac-440d-a93d-cba45a4d3e3e"
/>
## Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
---------
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
We’ve been watching Sidekiq workers climb from ~600 MB at boot to
1.4–1.5 GB after an hour whenever attachment-heavy jobs run. This PR is
an experiment to curb that growth by streaming attachments instead of
loading the whole blob into Ruby: reply-mailer inline attachments,
Telegram uploads, and audio transcriptions now read/write in chunks. If
this keeps RSS stable in production we’ll keep it; otherwise we’ll roll
it back and keep digging
- Enables outbound voice calls in voice channel . We are only caring
about wiring the logic to trigger outgoing calls to the call button
introduced in previous PRs. We will connect it to call component in
subsequent PRs
ref: #11602
## Screens
<img width="2304" height="1202" alt="image"
src="https://github.com/user-attachments/assets/b91543a8-8d4e-4229-bd80-9727b42c7b0f"
/>
<img width="2304" height="1200" alt="image"
src="https://github.com/user-attachments/assets/1a1dad2a-8cb2-4aa2-9702-c062416556a7"
/>
---------
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Vishnu Narayanan <vishnu@chatwoot.com>
This PR adds LLM instrumentation on langfuse for ai-editor feature
## Type of change
New feature (non-breaking change which adds functionality)
Needs langfuse account and env vars to be set
## How Has This Been Tested?
I configured personal langfuse credentials and instrumented the app,
traces can be seen in langfuse.
each conversation is one session.
<img width="1683" height="714" alt="image"
src="https://github.com/user-attachments/assets/3fcba1c9-63cf-44b9-a355-fd6608691559"
/>
<img width="1446" height="172" alt="image"
src="https://github.com/user-attachments/assets/dfa6e98f-4741-4e04-9a9e-078d1f01e97b"
/>
## Checklist:
- [x ] My code follows the style guidelines of this project
- [ x] I have performed a self-review of my code
- [ x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
---------
Co-authored-by: aakashb95 <aakash@chatwoot.com>
Co-authored-by: Vishnu Narayanan <iamwishnu@gmail.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
* chore: Hide "Learn More" button in feature spotlight for self-hosted (#12675)
* feat: single query for reporting event stats (#12664)
This PR collapses multiple queries fetching stats from a single table to
a single query
```sql
SELECT
user_id as user_id,
COUNT(CASE WHEN name = 'conversation_resolved' THEN 1 END) as resolved_count,
AVG(CASE WHEN name = 'conversation_resolved' THEN value END) as avg_resolution_time,
AVG(CASE WHEN name = 'first_response' THEN value END) as avg_first_response_time,
AVG(CASE WHEN name = 'reply_time' THEN value END) as avg_reply_time
FROM "reporting_events"
WHERE
"reporting_events"."account_id" = <account_id> AND
"reporting_events"."created_at" >= '2025-09-14 18:30:00' AND
"reporting_events"."created_at" < '2025-10-14 18:29:59'
GROUP BY "reporting_events"."user_id";
```
### Why this works?
Here's why this optimization is faster based on PostgreSQL internals:
- Single Table Scan vs Multiple Scans: Earlier we did 4 sequential scans
(or 4 index scans) of the same data, with the same where clause, now in
a single scan all 4 `CASE` expressions are evaluated in a single pass.
- Shared Buffer Cache Efficiency: PostgreSQL's shared buffer cache
stores recently accessed pages, with this, pages are loaded once and
re-used for all aggregation, earlier with separate queries we were
forced to re-read all from the disk each time
- Reduced planning and network overhead (4 vs 1 query)
### How is it tested
1. The specs all pass without making any changes
2. Verified the reports side by side after generating from report seeder
#### How to test
Generate seed data using the following command
```bash
ACCOUNT_ID=1 ENABLE_ACCOUNT_SEEDING=true bundle exec rake db:seed:reports_data
```
Once done download the reports, checkout to this branch and download the
reports again and compare them
* chore: Update translations (#12625)
* chore: Migrate mailers from the worker to jobs (#12331)
Previously, email replies were handled inside workers. There was no
execution logs. This meant if emails silently failed (as reported by a
customer), we had no way to trace where the issue happened, the only
assumption was “no error = mail sent.”
By moving email handling into jobs, we now have proper execution logs
for each attempt. This makes it easier to debug delivery issues and
would have better visibility when investigating customer reports.
Fixes
https://linear.app/chatwoot/issue/CW-5538/emails-are-not-sentdelivered-to-the-contact
---------
Co-authored-by: Sojan Jose <sojan@pepalo.com>
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
* chore(deps-dev): bump vite from 5.4.20 to 5.4.21 (#12700)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite)
from 5.4.20 to 5.4.21.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/releases">vite's
releases</a>.</em></p>
<blockquote>
<h2>v5.4.21</h2>
<p>Please refer to <a
href="https://github.com/vitejs/vite/blob/v5.4.21/packages/vite/CHANGELOG.md">CHANGELOG.md</a>
for details.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/vitejs/vite/blob/v5.4.21/packages/vite/CHANGELOG.md">vite's
changelog</a>.</em></p>
<blockquote>
<h2><!-- raw HTML omitted -->5.4.21 (2025-10-20)<!-- raw HTML omitted
--></h2>
<ul>
<li>fix(dev): trim trailing slash before <code>server.fs.deny</code>
check (<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/20968">#20968</a>)
(<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/20970">#20970</a>)
(<a
href="cad1d31d06">cad1d31</a>),
closes <a
href="https://redirect.github.com/vitejs/vite/issues/20968">#20968</a>
<a
href="https://redirect.github.com/vitejs/vite/issues/20970">#20970</a></li>
<li>chore: update CHANGELOG (<a
href="ca88ed7398">ca88ed7</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="adce3c22c6"><code>adce3c2</code></a>
release: v5.4.21</li>
<li><a
href="cad1d31d06"><code>cad1d31</code></a>
fix(dev): trim trailing slash before <code>server.fs.deny</code> check
(<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/20968">#20968</a>)
(<a
href="https://github.com/vitejs/vite/tree/HEAD/packages/vite/issues/20970">#20970</a>)</li>
<li><a
href="ca88ed7398"><code>ca88ed7</code></a>
chore: update CHANGELOG</li>
<li>See full diff in <a
href="https://github.com/vitejs/vite/commits/v5.4.21/packages/vite">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/chatwoot/chatwoot/network/alerts).
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore: Update translations (#12708)
* chore(sidekiq): log ActiveJob class and job_id on dequeue (#12704)
## Context
Sidekiq logs only showed the Sidekiq wrapper class and JID, which wasn’t
helpful when debugging ActiveJobs.
## Changes
- Updated `ChatwootDequeuedLogger` to log the actual `ActiveJob class`
and `job_id` instead of the generic Sidekiq wrapper and JID.
> Example
> ```
> Dequeued ActionMailer::MailDeliveryJob
123e4567-e89b-12d3-a456-426614174000 from default
> ```
- Remove sidekiq worker and unify everything to `ActiveJob`
* chore: Enforce custom role permissions on conversation access (#12583)
## Summary
- ensure conversation lookup uses the permission filter before fetching
records
- add request specs covering custom role access to unassigned
conversations
## Testing
- bundle exec rspec
spec/enterprise/controllers/api/v1/accounts/conversations_controller_spec.rb
------
https://chatgpt.com/codex/tasks/task_e_68de1f62b9b883268a54882e608a8bb8
* fix: parameterize agent name (#12709)
* chore: Remove channel icons from the create inbox page (#12727)
# Pull Request Template
## Description
This PR removes the frame containing all channel icons from the “Create
Inbox” page.
## Type of change
- [x] Bug fix (non-breaking change which fixes an issue)
## How Has This Been Tested?
### Screenshots
**Before**
<img width="1314" height="1016" alt="image"
src="https://github.com/user-attachments/assets/2b773495-9ddb-48b4-b15d-9aef18259ce1"
/>
**After**
<img width="1314" height="979" alt="image"
src="https://github.com/user-attachments/assets/f4dc64cf-516c-4faf-a45c-2f7de05cc29b"
/>
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
* fix: Use gap-4 instead of margins to define space between elements (#12728)
We should avoid using margins to define space between elements, instead
use the gap utility.
The problem with this particular instance was that if Google auth was
turned off and SSO is available, there is a weird spacing at the top
caused by the margin from the SSO element.
This PR will fix that. It also introduces a gap between the divider and
the button, but that should be okay.
* feat(ee): Add a service to fetch website content and prepare a persona of Captain Assistant (#12732)
This PR is the first of many to simplify the process of building an
assistant. The new flow will only require the user’s website. We’ll
automatically crawl it, identify the business name and what the business
does, and then generate a suggested assistant persona, complete with a
proposed name and description.
This service returns the following.
Example: tooljet.com
<img width="795" height="217" alt="Screenshot 2025-10-25 at 2 55 04 PM"
src="https://github.com/user-attachments/assets/9cb3594a-9c9c-4970-a0a1-4c9c8869c193"
/>
Example: replit.com
<img width="797" height="176" alt="Screenshot 2025-10-25 at 2 56 42 PM"
src="https://github.com/user-attachments/assets/6a1b4266-aab6-455f-a5e3-696d3a8243c9"
/>
* chore: Adds URL-based search and tab selection (#12663)
# Pull Request Template
## Description
This PR enables URL-based search and tab selection, allowing search
queries and active tabs to persist in the URL for easy sharing.
Fixes
[CW-5766](https://linear.app/chatwoot/issue/CW-5766/cannot-impersonate-an-account),
https://github.com/chatwoot/chatwoot/issues/12623
## Type of change
- [x] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
### Loom video
https://www.loom.com/share/422a1d61f3fe4278a88e352ef98d2b78?sid=35fabee7-652f-4e17-83bd-e066a3bb804c
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
* chore: Add tab params for inbox configuration (#12665)
# Pull Request Template
## Description
This PR enables active tabs in inbox settings to persist in the URL for
easy sharing.
## Type of change
- [x] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
### Loom video
https://www.loom.com/share/63820ecb17ea491a9082339f8bb457b6?sid=4fef1acd-b4fd-431f-855c-7647015a330f
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
---------
Co-authored-by: Muhsin <muhsinkeramam@gmail.com>
* feat: Changelog card components (#12673)
# Pull Request Template
## Description
This PR introduces a new changelog component that can be used in the
sidebar.
Fixes
https://linear.app/chatwoot/issue/CW-5776/changelog-card-ui-component
## Type of change
- [x] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
### Screencast
https://github.com/user-attachments/assets/42e77e82-388a-4fc9-9b37-f3d0ea1a9d7f
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
---------
Co-authored-by: Muhsin <muhsinkeramam@gmail.com>
* chore: Remove linear integration feature flag (#12716)
This PR removes the linear integration feature flag since the
integration is pretty much stable and we do display the Linear CTA for
users who aren't connected.
Fixes
https://linear.app/chatwoot/issue/CW-5819/remove-linear-feature-flag-from-front-end
* chore: Update translations (#12722)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
* perf: Add database index on conversations identifier (#12715)
**Problem**
Slack webhook processing was failing with 500 errors due to database
timeouts. The query `Conversation.where(identifier:
params[:event][:thread_ts]).first` was performing full table scans and
hitting PostgreSQL statement timeout.
**Solution**
Added database index on conversations.identifier and account_id.
* fix: Extend phone number normalization to Twilio WhatsApp (#12655)
### Problem
WhatsApp Cloud channels already handle Brazil/Argentina phone number
format mismatches (PRs #12492, #11173), but Twilio WhatsApp channels
were creating duplicate contacts
when:
- Template sent to new format: `whatsapp:+5541988887777` (13 digits)
- User responds from old format: `whatsapp:+554188887777` (12 digits)
### Solution
The solution extends the existing phone number normalization
infrastructure to support both WhatsApp providers while handling their
different payload formats:
### Provider Format Differences
- **WhatsApp Cloud**: `wa_id: "919745786257"` (clean number)
- **Twilio WhatsApp**: `From: "whatsapp:+919745786257"` (prefixed
format)
### Test Coverage
#### Brazil Phone Number Tests
**Case 1: New Format (13 digits with "9")**
- **Test 1**: No existing contact → Creates new contact with original
format
- **Test 2**: Contact exists in same format → Appends to existing
conversation
**Case 2: Old Format (12 digits without "9")**
- **Test 3**: Contact exists in old format → Appends to existing
conversation
- **Test 4** *(Critical)*: Contact exists in new format, message in old
format → Finds existing contact, prevents duplicate
- **Test 5**: No contact exists → Creates new contact with incoming
format
#### Argentina Phone Number Tests
**Case 3: With "9" after country code**
- **Test 6**: No existing contact → Creates new contact
- **Test 7**: Contact exists in normalized format → Uses existing
contact
**Case 4: Without "9" after country code**
- **Test 8**: Contact exists in same format → Appends to existing
- **Test 9**: No contact exists → Creates new contact
Fixes
https://linear.app/chatwoot/issue/CW-5565/inconsistencies-for-mobile-numbersargentina-brazil-and-mexico-numbers
* fix: Timezone offset reports broken by DST transition (#12747)
## Description
Fixes timezone offset parameter in V2 reports API that was broken by DST
transitions. The issue occurred when UK DST ended on October 26, 2025,
causing the test to fail starting October 27th.
~~**Initial diagnosis:** The root cause was that
`timezone_name_from_offset` used `zone.now.utc_offset` to match
timezones, which changes based on the current date's DST status rather
than the data being queried.~~
**Actual root cause:** The test was accidentally passing before DST
transition. During BST, `timezone_name_from_offset(0)` matched "Azores"
(UTC-1) instead of "Edinburgh" (UTC+0), and the -1 hour offset
coincidentally split midnight data into [1,5]. After DST ended, it
correctly matched "Edinburgh" (UTC+0), but this grouped all
conversations into one day [6], exposing that the test data was flawed.
The real issue: Test data created all 6 conversations starting at
midnight on a single day, which cannot produce a [1,5] split in true
UTC.
Fixes CW-5846
## Type of change
- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality not to work as expected)
- [ ] This change requires a documentation update
## How Has This Been Tested?
**Test that was failing:**
```bash
bundle exec rspec spec/controllers/api/v2/accounts/reports_controller_spec.rb:25
```
**Changes:**
~~1. Fixed `timezone_name_from_offset` to use January 1st as reference
date instead of current date~~
~~2. Converted timezone string to `ActiveSupport::TimeZone` object for
`group_by_period` compatibility~~
**Revised approach:**
1. Freeze test time to January 2024 using `travel_to`, making timezone
matching deterministic and aligned with test data period
2. Start test conversations at 23:00 instead of midnight to properly
span two days and test timezone boundary grouping
3. Keep `zone.now.utc_offset` (correct behavior for real users during
DST)
**Why this works:**
- Test runs "in January 2024" → `zone.now.utc_offset` returns January
offsets consistently
- Offset `-8` correctly matches Pacific Standard Time (UTC-8 in January)
- Real users in PDT (summer) with offset `-7` → correctly match Pacific
Daylight Time
- No production impact, test is deterministic year-round
**Verification:**
- Test now passes consistently regardless of current DST status
- Timezone matching works correctly for real users during DST periods
- Reports correctly group data by timezone offset across all seasons
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules
---------
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
* fix: Captain response builder not getting triggered (#12729)
## Summary
- Fix captain response builder not getting triggered for cases where
responses are created as completed.
## Testing Instructions
- Test articles with firecrawl
- Test articles without firecrawl
- Test PDF documents
---------
Co-authored-by: Pranav <pranav@chatwoot.com>
* chore: Update captain pending FAQ interface (#12752)
# Pull Request Template
## Description
**This PR includes,**
- Added new pending FAQs view with approve/edit/delete actions for each
response.
- Implemented banner notification showing pending FAQ count on main
approved responses page.
- Created dedicated route for pending FAQs review at
/captain/responses/pending.
- Added automatic pending count updates when switching assistants or
routes.
- Modified ResponseCard component to show action buttons instead of
dropdown in pending view.
Fixes
https://linear.app/chatwoot/issue/CW-5833/pending-faqs-in-a-different-ux
## Type of change
- [x] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
### Loom video
https://www.loom.com/share/5fe8f79b04cd4681b9360c48710b9373
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
---------
Co-authored-by: Pranav <pranav@chatwoot.com>
* fix: Exclude authentication templates from WhatsApp template selection (#12753)
This PR add the changes for excluding the authentication templates from
the WhatsApp template selection in the frontend, as these templates are
not supported at the moment. Reference:
https://www.chatwoot.com/hc/user-guide/articles/1754940076-whatsapp-templates#what-is-not-supported
* feat: Template types components (#12714)
# Pull Request Template
## Description
Fixes
https://linear.app/chatwoot/issue/CW-5806/create-the-story-book-components-for-template-typestext-media-list
**Pending**
Need to standardize the structure to match the template/campaigns.
## Type of change
- [x] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
### Screenshots
<img width="669" height="179" alt="image"
src="https://github.com/user-attachments/assets/42efd292-8520-4b05-81ec-8bc526fc12db"
/>
<img width="646" height="304" alt="image"
src="https://github.com/user-attachments/assets/431dd964-006c-4877-a693-dae39b90df4c"
/>
<img width="646" height="380" alt="image"
src="https://github.com/user-attachments/assets/9052e31f-9292-4afb-8897-13931655fa00"
/>
<img width="646" height="272" alt="image"
src="https://github.com/user-attachments/assets/873d2488-e856-4a0d-8579-cc1bcc61cc8e"
/>
<img width="646" height="490" alt="image"
src="https://github.com/user-attachments/assets/14c2aa42-bf27-475f-aa70-fe59c1d00e9b"
/>
<img width="646" height="281" alt="image"
src="https://github.com/user-attachments/assets/1f42408e-03e8-4863-b4c7-715d13d67686"
/>
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
* fix: update omniauth to latest to resolve heroku deployment issues (#12749)
# Pull Request Template
## Description
Fixes https://github.com/chatwoot/chatwoot/issues/12553
Heroku build was failing due to `omniauth` version mismatch. Also, added
`NODE_OPTIONS=--max-old-space-size=4096` to handle OOM during Vite
build.
## Type of change
Please delete options that are not relevant.
- [x] Bug fix (non-breaking change which fixes an issue)
## How Has This Been Tested?
- Tested on heroku
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules
* chore: Improvements in pending FAQs (#12755)
# Pull Request Template
## Description
**This PR includes:**
1. Added URL-based filter persistence for the responses pages, including
page and search parameters.
2. Introduced a new empty state variant for pending FAQs — without a
backdrop and with a “Clear Filters” option.
3. Made the actions, filter, and search row remain fixed at the top
while scrolling.
Fixes
https://linear.app/chatwoot/issue/CW-5852/improvements-in-pending-faqs
## Type of change
- [x] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
### Loom video
https://www.loom.com/share/1d9eee68c0684f0ab05e08b4ca1e0ce9
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
* fix: run captain v2 outside the transaction (#12756)
* feat: Always process email content (#12734)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
* feat: Bulk actions for contacts (#12763)
Introduces APIs and UI for bulk actions in contacts table. The initial
action available will be assign labels
Fixes: #8536#12253
## Screens
<img width="1350" height="747" alt="Screenshot 2025-10-29 at 4 05 08 PM"
src="https://github.com/user-attachments/assets/0792dff5-0371-4b2e-bdfb-cd32db773402"
/>
<img width="1345" height="717" alt="Screenshot 2025-10-29 at 4 05 19 PM"
src="https://github.com/user-attachments/assets/ae510404-c6de-4c15-a720-f6d10cdac25b"
/>
---------
Co-authored-by: Muhsin <muhsinkeramam@gmail.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
* feat: Enable opensearch on paid plans automatically (#12770)
- enable `advanced_search feature` on all paid plans automatically
ref: https://github.com/chatwoot/chatwoot/pull/12503
* chore: Make contacts bulk action bar sticky (#12773)
# Pull Request Template
## Description
This PR makes the contacts bulk action bar sticky while scrolling.
## Type of change
- [x] Bug fix (non-breaking change which fixes an issue)
## How Has This Been Tested?
### Screenshots
<img width="1080" height="300" alt="image"
src="https://github.com/user-attachments/assets/21f8f3c6-813e-4ef6-b40a-8dd14e6ffb26"
/>
<img width="1080" height="300" alt="image"
src="https://github.com/user-attachments/assets/bb939f1d-9a13-4f9f-953d-b9872c984b74"
/>
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
* chore: Add dependant destroy_async for sla events (#12774)
Added the destroy_async to prevent timeout during SLA policy deletion by
processing SLA events asynchronously.
* chore: Update translations (#12748)
* feat: Add company backfill migration for existing contacts (Part 1) (#12657)
## Description
Implements company backfill migration infrastructure for existing
contacts. This is **Part 1 of 2** for the company model production
rollout as described in
[CW-5726](https://linear.app/chatwoot/issue/CW-5726/company-model-setting-it-up-on-production).
Creates jobs and services to associate existing contacts with companies
based on their email domains, filtering out free email providers (gmail,
yahoo, etc.) and disposable addresses.
**What's included:**
- Business email detector service with ValidEmail2 (uses
`disposable_domain?` to avoid DNS lookups)
- Per-account batch job to process contacts for one account
- Orchestrator job to iterate all accounts
- Rake task: `bundle exec rake companies:backfill`
~~*NOTE*: I'm using a hard-coded approach to determine if something is a
"business" email by filtering out emails that are usually personal. I've
also added domains that are common to some of our customers' regions.
This should be simpler. I looked into `Valid_Email2` and I couldn't find
anything to dictate whether an email is a personal email or a business
one. I don't think the approach used in the frontend is valid here.~~
UPDATE: Using `email_provider_info` gem instead.
**Pending - Part 2 (separate PR):** Real-time company creation for new
contacts
## Type of change
- [x] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
```bash
# Run all new tests
bundle exec rspec spec/enterprise/services/companies/business_email_detector_service_spec.rb \\
spec/enterprise/jobs/migration/company_account_batch_job_spec.rb \\
spec/enterprise/jobs/migration/company_backfill_job_spec.rb
# Run RuboCop
bundle exec rubocop enterprise/app/services/companies/business_email_detector_service.rb \\
enterprise/app/jobs/migration/company_account_batch_job.rb \\
enterprise/app/jobs/migration/company_backfill_job.rb \\
lib/tasks/companies.rake
```
**Performance optimization:**
- Uses `disposable_domain?` instead of `disposable?` to avoid DNS MX
lookups (discovered via tcpdump analysis - `disposable?` was making
network calls for every email, causing 100x slowdown)
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
---------
Co-authored-by: Sojan Jose <sojan@pepalo.com>
* feat: Add company auto-association for contacts (CW-5726 Part 2) (#12711)
## Description
Implements real-time company auto-association for contacts based on
email domains. This is **Part 2** of the company model production
rollout (CW-5726).
**Task:**
- When a contact is created with a business email, automatically create
and associate a company from the email domain
- When a contact is updated with an email for the first time (email was
previously nil), associate with a company
- Preserve existing company associations when email changes to avoid
user confusion
- Skip free email providers and disposable domains
**Dependencies:**
⚠️ Requires PR #12657 (Part 1: Backfill migration) to be merged first
**Linear ticket:**
[CW-5726](https://linear.app/chatwoot/issue/CW-5726/company-model-setting-it-up-on-production)
## Type of change
- [x] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
- Service specs: Tests business email detection, company creation,
association logic, edge cases (existing companies, free emails, nil
emails)
- Integration specs: Tests full callback flow for contact create/update
scenarios
- All tests passing: 10 examples, 0 failures
- RuboCop: 0 offenses
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules (PR #12657 pending)
---------
Co-authored-by: Sojan Jose <sojan@pepalo.com>
* fix: Optimize Message search_data to prevent OpenSearch field explosion (#12786)
## Description
Refactored the `Message#search_data` method to prevent exceeding
OpenSearch's 1000 field limit during reindex operations.
**Problem:** The previous implementation serialized entire ActiveRecord
objects (Inbox, Sender, Conversation) with all their JSONB fields,
causing dynamic field explosion in OpenSearch. This resulted in
`Searchkick::ImportError` with "Limit of total fields [1000] has been
exceeded".
**Solution:** Whitelisted only necessary fields for search and
filtering, and flattened JSONB `custom_attributes` into key-value pair
arrays to prevent unbounded field creation.
Linked to: CW-5861
## Type of change
- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality not to work as expected)
- [x] This change requires a documentation update
## How Has This Been Tested?
- Verified rubocop passes with no offenses
- Code review of search field usage from
`enterprise/app/services/enterprise/search_service.rb`
- Analyzed actual search queries to determine required indexed fields
**Still needed:**
- Full reindex test on staging/production environment
- Verify search functionality still works after reindex
- Confirm field count is under 1000 limit
## Changes Made
### Before
- Indexed 1000+ fields (entire AR objects with JSONB)
- `inbox` = full Inbox object (23+ fields + JSONB)
- `sender` = full Contact/User/AgentBot object (10+ fields + JSONB)
- `conversation` = full push_event_data
- Dynamic JSONB keys creating unlimited fields
### After
- ~35-40 controlled fields
- Whitelisted search fields: `content`, `attachment_transcribed_text`,
`email_subject`
- Filter fields: `account_id`, `inbox_id`, `conversation_id`,
`sender_id`, `sender_type`, etc.
- Flattened `custom_attributes`: `[{key, value, value_type}]` format
- Helper methods: `search_conversation_data`, `search_inbox_data`,
`search_sender_data`, `search_additional_data`
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
## Post-merge Steps
After merging, the following steps are required:
1. **Reindex all messages:**
```bash
bundle exec rails runner "Message.reindex"
```
2. **Verify field count:**
```bash
bundle exec rails runner "
client = Searchkick.client
index_name = Message.searchkick_index.name
mapping = client.indices.get_mapping(index: index_name)
fields = mapping.dig(index_name, 'mappings', 'properties')
puts 'Total fields: ' + fields.keys.count.to_s
"
```
3. **Test search functionality** to ensure queries still work as
expected
---------
Co-authored-by: Vishnu Narayanan <iamwishnu@gmail.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
* fix: Avoid introducing new attributes in search (#12791)
Fix `Limit of total fields [1000] has been exceeded`
https://linear.app/chatwoot/issue/CW-5861/searchkickimporterror-type-=-illegal-argument-exception-reason-=-limit#comment-6b6e41bd
* fix: Gate Sidekiq dequeue logger behind env (#12790)
## Summary
- wrap the dequeue middleware registration in a boolean env flag
- document the ENABLE_SIDEKIQ_DEQUEUE_LOGGER option in .env.example
* feat: Bulk delete for contacts (#12778)
Introduces a new bulk action `delete` for contacts
ref: https://github.com/chatwoot/chatwoot/pull/12763
## Screens
<img width="1492" height="973" alt="Screenshot 2025-10-31 at 6 27 21 PM"
src="https://github.com/user-attachments/assets/30dab1bb-2c2c-4168-9800-44e0eb5f8e3a"
/>
<img width="1492" height="985" alt="Screenshot 2025-10-31 at 6 27 32 PM"
src="https://github.com/user-attachments/assets/5be610c4-b19e-4614-a164-103b22337382"
/>
* fix: Video bubble click and play issue (#12764)
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
* feat: Differentiate bot and user in the summary (#12801)
While generating the summary, use the appropriate sender type for the
message.
* fix: Invalid image URL issue in Help Center articles (#12806)
* feat: allow bots to handle campaigns when sender_id is nil (#12805)
* fix: Add empty line before signature in compose conversation editor (#12702)
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
* feat: Enhance button interactions (#12738)
* fix: Remove the same account validation for whatsapp channels (#12811)
## Description
Modified the phone number validation in Whatsapp::ChannelCreationService
to check for duplicate phone numbers across ALL accounts, not just
within the current account.
## Type of change
- [ ] Bug fix (non-breaking change which fixes an issue)
## How Has This Been Tested?
- Added test coverage for cross-account phone number validation
- Using actual UI flow
<img width="1493" height="532" alt="image"
src="https://github.com/user-attachments/assets/67d2bb99-2eb9-4115-8d56-449e4785e0d8"
/>
## Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
* feat: Update Captain navigation structure (#12761)
# Pull Request Template
## Description
This PR includes an update to the Captain navigation structure.
## Route Structure
```javascript
1. captain_assistants_responses_index → /captain/:assistantId/faqs
2. captain_assistants_documents_index → /captain/:assistantId/documents
3. captain_assistants_scenarios_index → /captain/:assistantId/scenarios
4. captain_assistants_playground_index → /captain/:assistantId/playground
5. captain_assistants_inboxes_index → /captain/:assistantId/inboxes
6. captain_tools_index → /captain/tools
7. captain_assistants_settings_index → /captain/:assistantId/settings
8. captain_assistants_guardrails_index → /captain/:assistantId/settings/guardrails
9. captain_assistants_guidelines_index → /captain/:assistantId/settings/guidelines
10. captain_assistants_index → /captain/:navigationPath
```
**How it works:**
1. User clicks sidebar item → Routes to `captain_assistants_index` with
`navigationPath`
2. `AssistantsIndexPage` validates route and gets last active assistant,
if not redirects to assistant create page.
3. Routes to actual page: `/captain/:assistantId/:page`
4. Page loads with correct assistant context
Fixes
https://linear.app/chatwoot/issue/CW-5832/updating-captain-navigation
## Type of change
- [x] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
---------
Co-authored-by: Pranav <pranav@chatwoot.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
* fix: Handle login when there are no accounts (#12816)
* chore: Update translations (#12794)
* chore(docs): Fix typos in some files (#12817)
This PR fixes typos in the file file using codespell.
* refactor: strategy pattern for mailbox conversation finding (#12766)
Co-authored-by: Pranav <pranav@chatwoot.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
* fix: Issue with processing variables in outgoing email content (#12799)
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
Co-authored-by: Vinay Keerthi <11478411+stonecharioteer@users.noreply.github.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
* fix: hide pdf citations in captain faq responses (#12839)
* fix: Use contact_id instead of sender_id for Instagram message locks (#12841)
Previously, the lock key for Instagram used sender_id, which for echo
messages (outgoing) would be the account's own ID. This caused all
outgoing messages to compete for the same lock, creating a bottleneck
during bulk messaging.
The fix introduces contact_instagram_id method that correctly identifies
the contact's ID regardless of message direction:
- For echo messages (outgoing): uses recipient.id (the contact)
- For incoming messages: uses sender.id (the contact)
This ensures each conversation has a unique lock, allowing parallel
processing of webhooks while maintaining race condition protection
within individual conversations.
Fixes lock acquisition errors in Sidekiq when processing bulk Instagram
messages.
Fixes
https://linear.app/chatwoot/issue/CW-5931/p0-mutexapplicationjoblockacquisitionerror-failed-to-acquire-lock-for
## Type of change
- [x] Bug fix (non-breaking change which fixes an issue)
* fix: label tags for contactable inboxes (#12838)
* chore: Improve captain layout (#12820)
* feat: allow selecting month range in overview reports (#12701)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
* fix: respect status parameter when creating articles via API (#12846)
## Description
The Articles API was ignoring the `status` parameter when creating new
articles. All articles were forced to be drafts due to a hardcoded
`@article.draft!` call in the controller, even when users explicitly
sent `status: 1` (published) in their API request.
This PR removes the hardcoded draft enforcement and allows the status
parameter to be respected while maintaining backward compatibility.
Fixes#12063
## Type of change
- [x] Bug fix (non-breaking change which fixes an issue)
## How Has This Been Tested?
**Before:**
- API POST with `status: 1` → Created as draft (ignored parameter)
- API POST without status → Created as draft
**After:**
- API POST with `status: 1` → Created as published ✅
- API POST without status → Created as draft (backward compatible) ✅
- UI creates articles → Still creates as draft (UI doesn't send status)
✅
**Tests run:**
```bash
bundle exec rspec spec/controllers/api/v1/accounts/articles_controller_spec.rb
# 17 examples, 0 failures
```
Updated tests:
1. Changed 2 existing tests that were verifying the broken behavior
(expecting draft when published was sent)
2. Added new test to verify articles default to draft when status is not
provided
3. All existing tests pass, confirming backward compatibility
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
Co-authored-by: Sojan Jose <sojan@pepalo.com>
* feat: allow querying reporting events via the API (#12832)
* feat(webhooks): add name to webhook (#12641)
## Description
When working with webhooks, it's easy to lose track of which URL is
which. Adding a `name` (optional) column to the webhook model is a
straight-forward solution to make it significantly easier to identify
webhooks.
## Type of change
- [x] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
Model and controller specs, and also running in production over several
months without any issues.
| Before | After |
| --- | --- |
| <img width="949" height="990" alt="image copy 3"
src="https://github.com/user-attachments/assets/6b33c072-7d16-4a9c-a129-f9c0751299f5"
/> | <img width="806" height="941" alt="image"
src="https://github.com/user-attachments/assets/77f3cb3a-2eb0-41ac-95bf-d02915589690"
/> |
| <img width="1231" height="650" alt="image copy 2"
src="https://github.com/user-attachments/assets/583374af-96e0-4436-b026-4ce79b7f9321"
/> | <img width="1252" height="650" alt="image copy"
src="https://github.com/user-attachments/assets/aa81fb31-fd18-4e21-a40e-d8ab0dc76b4e"
/> |
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules
* perf: speed up docker builds (#12859)
- Use separate keys to avoid cache overwrites across different
architecture builds
https://linear.app/chatwoot/issue/CW-5945/perf-speed-up-docker-builds
### 25 mins ---> 5mins
## before
<img width="971" height="452" alt="image"
src="https://github.com/user-attachments/assets/535cebd6-6c16-48d1-a62d-ffb6f2fc9b08"
/>
## after
<img width="940" height="428" alt="image"
src="https://github.com/user-attachments/assets/359eb313-4bb5-4e0e-9492-a8ad48645159"
/>
* chore: Update missing places with new colors (#12862)
# Pull Request Template
## Description
This PR updates the colors in places that were missed during the color
update migration.
## Type of change
- [x] Bug fix (non-breaking change which fixes an issue)
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
* fix: Brand installation name not showing (#12861)
# Pull Request Template
## Description
Fixes
https://linear.app/chatwoot/issue/CW-5946/fix-brand-installation-name-issue-in-dyte
## Type of change
- [x] Bug fix (non-breaking change which fixes an issue)
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
* fix: migrate from deprecated annotate gem to annotaterb (#12845)
## Description
The `annotate` gem has been deprecated and users are experiencing
annotation errors with the new Rails 7 `serialize` syntax. This PR
migrates to `annotaterb`, the actively maintained fork.
Users reported errors when running `make db`:
```
Unable to annotate app/models/installation_config.rb: no implicit conversion of Hash into String
Unable to annotate app/models/installation_config.rb: no implicit conversion of nil into Array
```
This PR updates the Gemfile and rake configuration to use `annotaterb`
instead.
Fixes#11673
## Type of change
- [x] Bug fix (non-breaking change which fixes an issue)
## How Has This Been Tested?
Tested locally with the following steps:
1. Run `bundle install` - successfully installed annotaterb 4.20.0
2. Run `RAILS_ENV=development bundle exec rails db:chatwoot_prepare` -
completed without annotation errors
3. Run `RAILS_ENV=development bundle exec rails annotate_rb:models` -
successfully annotated all models including InstallationConfig
4. Verified InstallationConfig model annotations are present and correct
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] My changes generate no new warnings
- [x] New and existing unit tests pass locally with my changes
* chore: disable worker MemoryHigh throttling in systemd unit (#12871)
- set MemoryHigh to infinity in deployment/chatwoot-worker.1.service so
the worker is throttled only by the existing
MemoryMax hard limit
- prevents cgroup reclaim from slowing Sidekiq under transient spikes
while still keeping the hard stop at 1.5 GB
* chore: Update translations (#12818)
* fix: revert annotaterb migration due to persistent annotation errors (#12881)
## Description
This PR reverts the migration from the `annotate` gem to `annotaterb`
introduced in PR #12845. The annotation errors reported in #11673
persist with both gems, and the old `annotate` gem handles the errors
more gracefully by continuing to process other models instead of
crashing.
**Testing reveals both gems fail with the same underlying issue:**
**Old annotate gem (3.2.0):**
```
Unable to annotate app/models/installation_config.rb: no implicit conversion of Hash into String
Unable to annotate app/models/installation_config.rb: no implicit conversion of nil into Array
Model files unchanged.
```
(Logs error but continues processing)
**New annotaterb gem (4.20.0):**
```
❯ bundle exec annotaterb models
ruby/3.4.4/lib/ruby/gems/3.4.0/gems/reline-0.3.6/lib/reline/terminfo.rb:2: warning: ruby/3.4.4/lib/ruby/3.4.0/fiddle.rb was loaded from the standard library, but will no longer be part of the default gems starting from Ruby 3.5.0.
You can add fiddle to your Gemfile or gemspec to silence this warning.
Also please contact the author of reline-0.3.6 to request adding fiddle into its gemspec.
Annotating models
bundler: failed to load command: annotaterb (ruby/3.4.4/bin/annotaterb)
ruby/3.4.4/lib/ruby/3.4.0/psych/parser.rb:62:in 'Psych::Parser#_native_parse': no implicit conversion of Hash into String (TypeError)
_native_parse @handler, yaml, path
^^^^^^^^^^^^^^^^^^^^
from ruby/3.4.4/lib/ruby/3.4.0/psych/parser.rb:62:in 'Psych::Parser#parse'
from ruby/3.4.4/lib/ruby/3.4.0/psych.rb:457:in 'Psych.parse_stream'
from ruby/3.4.4/lib/ruby/3.4.0/psych.rb:401:in 'Psych.parse'
from ruby/3.4.4/lib/ruby/3.4.0/psych.rb:325:in 'Psych.safe_load'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activerecord-7.1.5.2/lib/active_record/coders/yaml_column.rb:37:in 'ActiveRecord::Coders::YAMLColumn::SafeCoder#load'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activerecord-7.1.5.2/lib/active_record/coders/column_serializer.rb:37:in 'ActiveRecord::Coders::ColumnSerializer#load'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activerecord-7.1.5.2/lib/active_record/type/serialized.rb:22:in 'ActiveRecord::Type::Serialized#deserialize'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activemodel-7.1.5.2/lib/active_model/attribute.rb:175:in 'ActiveModel::Attribute::FromDatabase#type_cast'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activemodel-7.1.5.2/lib/active_model/attribute.rb:43:in 'ActiveModel::Attribute#value'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activemodel-7.1.5.2/lib/active_model/attribute_set.rb:37:in 'block in ActiveModel::AttributeSet#to_hash'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activesupport-7.1.5.2/lib/active_support/core_ext/enumerable.rb:78:in 'block in Enumerable#index_with'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activesupport-7.1.5.2/lib/active_support/core_ext/enumerable.rb:78:in 'Array#each'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activesupport-7.1.5.2/lib/active_support/core_ext/enumerable.rb:78:in 'Enumerable#index_with'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activemodel-7.1.5.2/lib/active_model/attribute_set.rb:37:in 'ActiveModel::AttributeSet#to_hash'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/activerecord-7.1.5.2/lib/active_record/model_schema.rb:499:in 'ActiveRecord::ModelSchema::ClassMethods#column_defaults'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/annotaterb-4.20.0/lib/annotate_rb/model_annotator/model_wrapper.rb:68:in 'AnnotateRb::ModelAnnotator::ModelWrapper#column_defaults'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/annotaterb-4.20.0/lib/annotate_rb/model_annotator/model_wrapper.rb:139:in 'block in AnnotateRb::ModelAnnotator::ModelWrapper#built_attributes'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/annotaterb-4.20.0/lib/annotate_rb/model_annotator/model_wrapper.rb:136:in 'Array#map'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/annotaterb-4.20.0/lib/annotate_rb/model_annotator/model_wrapper.rb:136:in 'AnnotateRb::ModelAnnotator::ModelWrapper#built_attributes'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/annotaterb-4.20.0/lib/annotate_rb/model_annotator/column_annotation/annotation_builder.rb:15:in 'AnnotateRb::ModelAnnotator::ColumnAnnotation::AnnotationBuilder#build'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/annotaterb-4.20.0/lib/annotate_rb/model_annotator/annotation/annotation_builder.rb:52:in 'block in AnnotateRb::ModelAnnotator::Annotation::AnnotationBuilder::Annotation#columns'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/annotaterb-4.20.0/lib/annotate_rb/model_annotator/annotation/annotation_builder.rb:51:in 'Array#map'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/annotaterb-4.20.0/lib/annotate_rb/model_annotator/annotation/annotation_builder.rb:51:in 'AnnotateRb::ModelAnnotator::Annotation::AnnotationBuilder::Annotation#columns'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/annotaterb-4.20.0/lib/annotate_rb/model_annotator/annotation/annotation_builder.rb:26:in 'AnnotateRb::ModelAnnotator::Annotation::AnnotationBuilder::Annotation#body'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/annotaterb-4.20.0/lib/annotate_rb/model_annotator/annotation/annotation_builder.rb:35:in 'AnnotateRb::ModelAnnotator::Annotation::AnnotationBuilder::Annotation#build'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/annotaterb-4.20.0/lib/annotate_rb/model_annotator/annotation/annotation_builder.rb:71:in 'AnnotateRb::ModelAnnotator::Annotation::AnnotationBuilder#build'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/annotaterb-4.20.0/lib/annotate_rb/model_annotator/project_annotator.rb:43:in 'AnnotateRb::ModelAnnotator::ProjectAnnotator#build_instructions_for_file'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/annotaterb-4.20.0/lib/annotate_rb/model_annotator/project_annotator.rb:17:in 'block in AnnotateRb::ModelAnnotator::ProjectAnnotator#annotate'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/annotaterb-4.20.0/lib/annotate_rb/model_annotator/project_annotator.rb:13:in 'Array#map'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/annotaterb-4.20.0/lib/annotate_rb/model_annotator/project_annotator.rb:13:in 'AnnotateRb::ModelAnnotator::ProjectAnnotator#annotate'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/annotaterb-4.20.0/lib/annotate_rb/model_annotator/annotator.rb:21:in 'AnnotateRb::ModelAnnotator::Annotator#do_annotations'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/annotaterb-4.20.0/lib/annotate_rb/model_annotator/annotator.rb:8:in 'AnnotateRb::ModelAnnotator::Annotator.do_annotations'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/annotaterb-4.20.0/lib/annotate_rb/commands/annotate_models.rb:17:in 'AnnotateRb::Commands::AnnotateModels#call'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/annotaterb-4.20.0/lib/annotate_rb/runner.rb:38:in 'AnnotateRb::Runner#run'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/annotaterb-4.20.0/lib/annotate_rb/runner.rb:11:in 'AnnotateRb::Runner.run'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/annotaterb-4.20.0/exe/annotaterb:18:in '<top (required)>'
from ruby/3.4.4/bin/annotaterb:25:in 'Kernel#load'
from ruby/3.4.4/bin/annotaterb:25:in '<top (required)>'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/bundler-2.5.16/lib/bundler/cli/exec.rb:58:in 'Kernel.load'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/bundler-2.5.16/lib/bundler/cli/exec.rb:58:in 'Bundler::CLI::Exec#kernel_load'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/bundler-2.5.16/lib/bundler/cli/exec.rb:23:in 'Bundler::CLI::Exec#run'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/bundler-2.5.16/lib/bundler/cli.rb:455:in 'Bundler::CLI#exec'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/bundler-2.5.16/lib/bundler/vendor/thor/lib/thor/command.rb:28:in 'Bundler::Thor::Command#run'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/bundler-2.5.16/lib/bundler/vendor/thor/lib/thor/invocation.rb:127:in 'Bundler::Thor::Invocation#invoke_command'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/bundler-2.5.16/lib/bundler/vendor/thor/lib/thor.rb:527:in 'Bundler::Thor.dispatch'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/bundler-2.5.16/lib/bundler/cli.rb:35:in 'Bundler::CLI.dispatch'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/bundler-2.5.16/lib/bundler/vendor/thor/lib/thor/base.rb:584:in 'Bundler::Thor::Base::ClassMethods#start'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/bundler-2.5.16/lib/bundler/cli.rb:29:in 'Bundler::CLI.start'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/bundler-2.5.16/exe/bundle:28:in 'block in <top (required)>'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/bundler-2.5.16/lib/bundler/friendly_errors.rb:117:in 'Bundler.with_friendly_errors'
from ruby/3.4.4/lib/ruby/gems/3.4.0/gems/bundler-2.5.16/exe/bundle:20:in '<top (required)>'
from ruby/3.4.4/bin/bundle:25:in 'Kernel#load'
from ruby/3.4.4/bin/bundle:25:in '<main>'
```
(Crashes immediately, stops all processing)
**Root cause:** The `InstallationConfig` model uses YAML serialization
(`serialize :serialized_value, coder: YAML`) on a JSONB database column.
When annotation tools read column defaults, PostgreSQL returns JSONB as
a Hash, but YAML expects a String, causing the type error.
The migration to annotaterb doesn't solve the problem - both gems
encounter the same error. The old gem is preferable as it continues
working despite the error.
Reverts #12845
Related to #11673
## Type of change
- [x] Bug fix (non-breaking change which fixes an issue)
## How Has This Been Tested?
1. Reverted commit 559d1b657
2. Ran `bundle install` to reinstall annotate gem v3.2.0
3. Ran `RAILS_ENV=development bundle exec annotate`
- Result: Logs errors for InstallationConfig but completes successfully
4. Re-applied the annotaterb changes and tested `bundle exec annotaterb
models`
- Result: Crashes with full stack trace and stops processing
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] My changes generate no new warnings
- [x] New and existing unit tests pass locally with my changes
---
*Edited to truncate environment-specific info from error dump*
* chore: Hide assistant switcher on paywall screen (#12875)
* feat: Assignment service (v2) (#12320)
## Linear Link
## Description
This PR introduces a new robust auto-assignment system for conversations
in Chatwoot. The system replaces the existing round-robin assignment
with a more sophisticated service-based architecture that supports
multiple assignment strategies, rate limiting, and Enterprise features
like capacity-based assignment and balanced distribution.
## Type of change
- [ ] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
- Unit test cases
- Test conversations getting assigned on status change to open
- Test the job directly via rails console
## Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Adds a new service-based auto-assignment system with scheduled jobs,
rate limiting, enterprise capacity/balanced selection, and wiring via
inbox/handler; includes Redis helpers and comprehensive tests.
>
> - **Auto-assignment v2 (core services)**:
> - Add `AutoAssignment::AssignmentService` with bulk assignment,
configurable conversation priority, RR selection, and per-agent rate
limiting via `AutoAssignment::RateLimiter`.
> - Add `AutoAssignment::RoundRobinSelector` for agent selection.
> - **Jobs & scheduling**:
> - Add `AutoAssignment::AssignmentJob` (per-inbox bulk assign;
env-based limit) and `AutoAssignment::PeriodicAssignmentJob` (batch over
accounts/inboxes).
> - Schedule periodic run in `config/schedule.yml`
(`periodic_assignment_job`).
> - **Model/concerns wiring**:
> - Include `InboxAgentAvailability` in `Inbox`; add
`Inbox#auto_assignment_v2_enabled?`.
> - Update `AutoAssignmentHandler` to trigger v2 job when
`auto_assignment_v2_enabled?`, else fallback to legacy.
> - **Enterprise extensions**:
> - Add `Enterprise::InboxAgentAvailability` (capacity-aware filtering)
and `Enterprise::Concerns::Inbox` association `inbox_capacity_limits`.
> - Extend service via `Enterprise::AutoAssignment::AssignmentService`
(policy-driven config, capacity filtering, exclusion rules) and add
selectors/services: `BalancedSelector`, `CapacityService`.
> - **Infrastructure**:
> - Enhance `Redis::Alfred` with `expire`, key scan/count, and extended
ZSET helpers (`zadd`, `zcount`, `zcard`, `zrangebyscore`).
> - **Tests**:
> - Add specs for jobs, core service, rate limiter, RR selector, and
enterprise features (capacity, balanced selection, exclusions).
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
0ebe187c8aea73765b0122a44b18d6f465c2477f. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
* fix: Change contact_inboxes.source_id to text column (#12882)
## Description
Fixes CW-5961 where IMAP email processing failed with
`ActiveRecord::RecordInvalid: Validation failed: Source is too long
(maximum is 255 characters)` error.
This changes the `contact_inboxes.source_id` column from `string` (255
character limit) to `text` (unlimited) to accommodate long email message
IDs that were causing validation failures.
Fixes CW-5961
## Type of change
- [x] Bug fix (non-breaking change which fixes an issue)
## How Has This Been Tested?
- Added spec test validating `source_id` values longer than 255
characters (300 chars)
- All existing `contact_inbox_spec.rb` tests pass (7 examples, 0
failures)
- Migration applied successfully with reversible up/down methods
- Verified `source_id` column type changed to `text` with `null: false`
constraint preserved
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
* feat: allow configuring attachment upload limit (#12835)
## Summary
- add a configurable MAXIMUM_FILE_UPLOAD_SIZE installation setting and
surface it through super admin and global config payloads
- apply the configurable limit to attachment validations and shared
upload helpers on dashboard and widget
- introduce a reusable helper with unit tests for parsing the limit and
extend attachment specs for configurability
------
[Codex
Task](https://chatgpt.com/codex/tasks/task_e_6912644786b08326bc8dee9401af6d0a)
---------
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
* feat: Customizable webhook timeout configuration (#12777)
## Summary
- Ability to configure the webhook timeout for Chatwoot self hosted
installations
fixes: https://github.com/chatwoot/chatwoot/issues/12754
* feat: Control the allowed login methods via Super Admin (#12892)
- Control the allowed authentication methods for a chatwoot installation
via super admin configs. [SAML, Google Auth etc]
------
[Codex
Task](https://chatgpt.com/codex/tasks/task_e_6917d503b6e48326a261672c1de91462)
---------
Co-authored-by: Pranav <pranav@chatwoot.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
* chore: Update translations (#12876)
* feat: Backend - Companies API endpoint with pagination and search (#12840)
## Description
Adds API endpoint to list companies with pagination, search, and
sorting.
Fixes
https://linear.app/chatwoot/issue/CW-5930/add-backend-routes-to-get-companies-result
Parent issue:
https://linear.app/chatwoot/issue/CW-5928/add-companies-tab-to-dashboard
## Type of change
- [x] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
Added comprehensive specs to
`spec/enterprise/controllers/api/v1/accounts/companies_controller_spec.rb`:
- Pagination (25 per page, multiple pages)
- Search by name and domain (case-insensitive)
- Counter cache for contacts_count
- Account scoping
- Authorization
To reproduce:
```bash
bundle exec rspec spec/enterprise/controllers/api/v1/accounts/companies_controller_spec.rb
bundle exec rubocop enterprise/app/controllers/api/v1/accounts/companies_controller.rb
```
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
---------
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
* feat: Companies page (#12842)
# Pull Request Template
## Description
This PR introduces a new Companies section in the Chatwoot dashboard. It
lists all companies associated with the account and includes features
such as **search**, **sorting**, and **pagination** to enable easier
navigation and efficient management.
Fixes
https://linear.app/chatwoot/issue/CW-5928/add-companies-tab-to-dashboard
## Type of change
- [x] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
### Screenshot
<img width="1619" height="1200" alt="image"
src="https://github.com/user-attachments/assets/21f0a666-c3d6-4dec-bd02-1e38e0cd9542"
/>
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
---------
Co-authored-by: Vinay Keerthi <11478411+stonecharioteer@users.noreply.github.com>
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
* feat: Add Amazon SES inbound email support (#12893)
## Summary
- add AWS ActionMailbox SES gems
- document SES as incoming email provider
- note SES option in configuration
## Testing
- `bundle exec rubocop config/initializers/mailer.rb
config/environments/production.rb Gemfile`
------
[Codex
Task](https://chatgpt.com/codex/tasks/task_e_68bbb7d482288326b8f04bb795af0322)
---------
Co-authored-by: Pranav <pranav@chatwoot.com>
Co-authored-by: Vinay Keerthi <11478411+stonecharioteer@users.noreply.github.com>
* feat: hide email forwarding address if INBOUND_EMAIL_DOMAIN is not configured (#12768)
#### Summary
- Improved email inbox setup flow to handle cases where inbound email
forwarding is not configured on the installation
- Added conditional display of email forwarding address based on
MAILER_INBOUND_EMAIL_DOMAIN environment variable availability
- Enhanced user messaging to guide users toward configuring SMTP/IMAP
settings when forwarding is unavailable
#### Changes
**Backend (app/views/api/v1/models/_inbox.json.jbuilder)**
- Added forwarding_enabled boolean flag to inbox API response based on
MAILER_INBOUND_EMAIL_DOMAIN presence
- Made forward_to_email conditional - only included when forwarding is
enabled
**Frontend - Inbox Creation Flow**
- Created new EmailInboxFinish.vue component to handle email inbox setup
completion
- Shows different messages based on whether forwarding is enabled:
- With forwarding: displays forwarding address and encourages SMTP/IMAP
configuration
- Without forwarding: warns that SMTP/IMAP configuration is required for
emails to be processed
- Added link to configuration page for easy access to SMTP/IMAP settings
<img width="988" height="312" alt="Screenshot 2025-11-18 at 3 27 27 PM"
src="https://github.com/user-attachments/assets/928aff78-df73-49fa-9a26-dbbd1297b26a"
/>
<img width="765" height="489" alt="Screenshot 2025-11-18 at 3 24 46 PM"
src="https://github.com/user-attachments/assets/6a182c7d-087f-4e88-92a5-30f147a567a7"
/>
Fixes
https://linear.app/chatwoot/issue/CW-5881/hide-forwaring-email-section-if-inbound-email-domain-is-not-configured
## Type of change
- [x] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
- Tested locally
## Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
---------
Co-authored-by: Pranav <pranav@chatwoot.com>
* feat: APIs to assign agents_bots as assignee in conversations (#12836)
## Summary
- add an assignee_agent_bot_id column as an initital step to prototype
this before fully switching to polymorphic assignee
- update assignment APIs and conversation list / show endpoints to
reflect assignee as agent bot
- ensure webhook payloads contains agent bot assignee
[Codex
Task](https://chatgpt.com/codex/tasks/task_e_6912833377e48326b6641b9eee32d50f)
---------
Co-authored-by: Pranav <pranav@chatwoot.com>
* Bump version to 4.8.0
* chore: remove migration
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
Co-authored-by: Chatwoot Bot <92152627+chatwoot-bot@users.noreply.github.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Muhsin <muhsinkeramam@gmail.com>
Co-authored-by: Vinay Keerthi <11478411+stonecharioteer@users.noreply.github.com>
Co-authored-by: Vishnu Narayanan <iamwishnu@gmail.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Tanmay Deep Sharma <32020192+tds-1@users.noreply.github.com>
Co-authored-by: Lê Nam Khánh <55955273+khanhkhanhlele@users.noreply.github.com>
## Description
Adds API endpoint to list companies with pagination, search, and
sorting.
Fixes
https://linear.app/chatwoot/issue/CW-5930/add-backend-routes-to-get-companies-result
Parent issue:
https://linear.app/chatwoot/issue/CW-5928/add-companies-tab-to-dashboard
## Type of change
- [x] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
Added comprehensive specs to
`spec/enterprise/controllers/api/v1/accounts/companies_controller_spec.rb`:
- Pagination (25 per page, multiple pages)
- Search by name and domain (case-insensitive)
- Counter cache for contacts_count
- Account scoping
- Authorization
To reproduce:
```bash
bundle exec rspec spec/enterprise/controllers/api/v1/accounts/companies_controller_spec.rb
bundle exec rubocop enterprise/app/controllers/api/v1/accounts/companies_controller.rb
```
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
---------
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
- Control the allowed authentication methods for a chatwoot installation
via super admin configs. [SAML, Google Auth etc]
------
[Codex
Task](https://chatgpt.com/codex/tasks/task_e_6917d503b6e48326a261672c1de91462)
---------
Co-authored-by: Pranav <pranav@chatwoot.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
## Linear Link
## Description
This PR introduces a new robust auto-assignment system for conversations
in Chatwoot. The system replaces the existing round-robin assignment
with a more sophisticated service-based architecture that supports
multiple assignment strategies, rate limiting, and Enterprise features
like capacity-based assignment and balanced distribution.
## Type of change
- [ ] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
- Unit test cases
- Test conversations getting assigned on status change to open
- Test the job directly via rails console
## Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Adds a new service-based auto-assignment system with scheduled jobs,
rate limiting, enterprise capacity/balanced selection, and wiring via
inbox/handler; includes Redis helpers and comprehensive tests.
>
> - **Auto-assignment v2 (core services)**:
> - Add `AutoAssignment::AssignmentService` with bulk assignment,
configurable conversation priority, RR selection, and per-agent rate
limiting via `AutoAssignment::RateLimiter`.
> - Add `AutoAssignment::RoundRobinSelector` for agent selection.
> - **Jobs & scheduling**:
> - Add `AutoAssignment::AssignmentJob` (per-inbox bulk assign;
env-based limit) and `AutoAssignment::PeriodicAssignmentJob` (batch over
accounts/inboxes).
> - Schedule periodic run in `config/schedule.yml`
(`periodic_assignment_job`).
> - **Model/concerns wiring**:
> - Include `InboxAgentAvailability` in `Inbox`; add
`Inbox#auto_assignment_v2_enabled?`.
> - Update `AutoAssignmentHandler` to trigger v2 job when
`auto_assignment_v2_enabled?`, else fallback to legacy.
> - **Enterprise extensions**:
> - Add `Enterprise::InboxAgentAvailability` (capacity-aware filtering)
and `Enterprise::Concerns::Inbox` association `inbox_capacity_limits`.
> - Extend service via `Enterprise::AutoAssignment::AssignmentService`
(policy-driven config, capacity filtering, exclusion rules) and add
selectors/services: `BalancedSelector`, `CapacityService`.
> - **Infrastructure**:
> - Enhance `Redis::Alfred` with `expire`, key scan/count, and extended
ZSET helpers (`zadd`, `zcount`, `zcard`, `zrangebyscore`).
> - **Tests**:
> - Add specs for jobs, core service, rate limiter, RR selector, and
enterprise features (capacity, balanced selection, exclusions).
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
0ebe187c8aea73765b0122a44b18d6f465c2477f. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
## Description
Implements real-time company auto-association for contacts based on
email domains. This is **Part 2** of the company model production
rollout (CW-5726).
**Task:**
- When a contact is created with a business email, automatically create
and associate a company from the email domain
- When a contact is updated with an email for the first time (email was
previously nil), associate with a company
- Preserve existing company associations when email changes to avoid
user confusion
- Skip free email providers and disposable domains
**Dependencies:**
⚠️ Requires PR #12657 (Part 1: Backfill migration) to be merged first
**Linear ticket:**
[CW-5726](https://linear.app/chatwoot/issue/CW-5726/company-model-setting-it-up-on-production)
## Type of change
- [x] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
- Service specs: Tests business email detection, company creation,
association logic, edge cases (existing companies, free emails, nil
emails)
- Integration specs: Tests full callback flow for contact create/update
scenarios
- All tests passing: 10 examples, 0 failures
- RuboCop: 0 offenses
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules (PR #12657 pending)
---------
Co-authored-by: Sojan Jose <sojan@pepalo.com>
## Description
Implements company backfill migration infrastructure for existing
contacts. This is **Part 1 of 2** for the company model production
rollout as described in
[CW-5726](https://linear.app/chatwoot/issue/CW-5726/company-model-setting-it-up-on-production).
Creates jobs and services to associate existing contacts with companies
based on their email domains, filtering out free email providers (gmail,
yahoo, etc.) and disposable addresses.
**What's included:**
- Business email detector service with ValidEmail2 (uses
`disposable_domain?` to avoid DNS lookups)
- Per-account batch job to process contacts for one account
- Orchestrator job to iterate all accounts
- Rake task: `bundle exec rake companies:backfill`
~~*NOTE*: I'm using a hard-coded approach to determine if something is a
"business" email by filtering out emails that are usually personal. I've
also added domains that are common to some of our customers' regions.
This should be simpler. I looked into `Valid_Email2` and I couldn't find
anything to dictate whether an email is a personal email or a business
one. I don't think the approach used in the frontend is valid here.~~
UPDATE: Using `email_provider_info` gem instead.
**Pending - Part 2 (separate PR):** Real-time company creation for new
contacts
## Type of change
- [x] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
```bash
# Run all new tests
bundle exec rspec spec/enterprise/services/companies/business_email_detector_service_spec.rb \\
spec/enterprise/jobs/migration/company_account_batch_job_spec.rb \\
spec/enterprise/jobs/migration/company_backfill_job_spec.rb
# Run RuboCop
bundle exec rubocop enterprise/app/services/companies/business_email_detector_service.rb \\
enterprise/app/jobs/migration/company_account_batch_job.rb \\
enterprise/app/jobs/migration/company_backfill_job.rb \\
lib/tasks/companies.rake
```
**Performance optimization:**
- Uses `disposable_domain?` instead of `disposable?` to avoid DNS MX
lookups (discovered via tcpdump analysis - `disposable?` was making
network calls for every email, causing 100x slowdown)
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
---------
Co-authored-by: Sojan Jose <sojan@pepalo.com>
## Summary
- Fix captain response builder not getting triggered for cases where
responses are created as completed.
## Testing Instructions
- Test articles with firecrawl
- Test articles without firecrawl
- Test PDF documents
---------
Co-authored-by: Pranav <pranav@chatwoot.com>
This PR is the first of many to simplify the process of building an
assistant. The new flow will only require the user’s website. We’ll
automatically crawl it, identify the business name and what the business
does, and then generate a suggested assistant persona, complete with a
proposed name and description.
This service returns the following.
Example: tooljet.com
<img width="795" height="217" alt="Screenshot 2025-10-25 at 2 55 04 PM"
src="https://github.com/user-attachments/assets/9cb3594a-9c9c-4970-a0a1-4c9c8869c193"
/>
Example: replit.com
<img width="797" height="176" alt="Screenshot 2025-10-25 at 2 56 42 PM"
src="https://github.com/user-attachments/assets/6a1b4266-aab6-455f-a5e3-696d3a8243c9"
/>
# Pull Request Template
## Description
* add Company model with validations for name, domain, description and
avatar
* Add database migration fo
* Implement endpoints for company CRUD operations
* Add optional company relationship for contacts
* Add test for models, controllers, factories and policies
* Add authorization policies restricting delete to admins
* support JSON API responses
Please include a summary of the change and issue(s) fixed. Also, mention
relevant motivation, context, and any dependencies that this change
requires.
Fixes #(cw-5650)
## Type of change
Please delete options that are not relevant.
- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality not to work as expected)
- [ ] This change requires a documentation update
## How Has This Been Tested?
Tests are implemented using `RSpec`
```
$ bundle exec rails db:migrate
$ bundle exec rspec spec/models/company_spec.rb spec/controllers/api/v1/accounts/companies_controller_spec.rb
```
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
With this change, the indexing would be separate from the search, so you
need to enable indexing on the cloud and run it. It should start
indexing the messages to ElasticSearch/OpenSearch. Once indexing is
completed, we can turn on the feature for the customer.
Make sure that the following is done when you deploy.
Set POSTGRES_STATEMENT_TIMEOUT=600s before you run the indexing.
1. Make sure that the account with advanced_search has
advanced_search_indexing enabled
```rb
Account.feature_advanced_search.each do |account|
account.enable_features(:advanced_search_indexing)
account.save!
end
```
2. Enable indexing for all accounts with paid subscription.
```rb
Account.where("custom_attributes ->> 'plan_name' IN (?)", ['Enterprise', 'Startups', 'Business']).each do |account|
account.enable_features(:advanced_search_indexing)
account.save!
end
```
3. Run indexing for all the messages.
```rb
Message.reindex
```
Co-authored-by: Vishnu Narayanan <iamwishnu@gmail.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
## Linear Link:
https://linear.app/chatwoot/issue/CW-5636/pdf-faqs-captain-generates-faqs-in-the-english-only
## Description
PDF Faqs should be generated in the same language as set in account
## Type of change
- [ ] Bug fix (non-breaking change which fixes an issue)
## How Has This Been Tested?
This has been tested via UI, by setting account language to arabic and
upload the pdf for faq generation (pdf content in Hindi)
<img width="1045" height="1085" alt="image"
src="https://github.com/user-attachments/assets/10385181-578e-4933-afc4-4609a6abcec8"
/>
## Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
## Description
This fixes an oversight from my previous PR #12120 where I forgot to add
the config key to the enterprise config controller, leading to the
configuration not being showed in the super admin panel.
## Type of change
- [x] Bug fix (non-breaking change which fixes an issue)
## How Has This Been Tested?
I believe this is the correct code change but haven't ran a full test
due to hardware constraints. It would be ideal if you could perform a
quick check on this.
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
## Description
This PR sets up an `Enterprise::Railtie` to correctly register rake
tasks in the `enterprise` namespace.
Previously, rake tasks under `enterprise/lib/tasks` were being eagerly
loaded at Rails boot, causing `undefined method 'namespace'` errors.
With this change, rake tasks are now registered only in the rake
context, avoiding boot-time issues and ensuring they are discoverable
with `bin/rake -T`.
**Tasks added:**
* `search:all` → Reindex messages for all accounts
* `search:account[ID]` → Reindex messages for a specific account
Fixes: #12414
Co-authored-by: Sojan Jose <sojan@pepalo.com>
I've added the account_id filter to the
`get_agent_ids_over_assignment_limit` method. This optimization will
help the query leverage the existing composite index
`conv_acid_inbid_stat_asgnid_idx (account_id, inbox_id, status,
assignee_id)` for better performance.
**Before:**
```sql
HashAggregate (cost=224238.12..224256.27 rows=484 width=4)
Group Key: assignee_id
Filter: (count(*) >= 10)
-> Index Scan using index_conversations_on_inbox_id on conversations (cost=0.44..223963.67 rows=54891 width=4)
Index Cond: (inbox_id = ???)
Filter: (status = 0)
```
**After:**
```sql
GroupAggregate (cost=0.44..5688.30 rows=476 width=4)
Group Key: assignee_id
Filter: (count(*) >= 10)
-> Index Only Scan using conv_acid_inbid_stat_asgnid_idx on conversations (cost=0.44..5640.81 rows=5928 width=4)
Index Cond: ((account_id = ??) AND (inbox_id = ??) AND (status = 0))
```
This PR delivers the first slice of the voice channel: inbound call
handling. When a customer calls a configured voice
number, Chatwoot now creates a new conversation and shows a dedicated
call bubble in the UI. As the call progresses
(ringing, answered, completed), its status updates in real time in both
the conversation list and the call bubble, so
agents can instantly see what’s happening. This focuses on the inbound
flow and is part of breaking the larger voice
feature into smaller, functional, and testable units; further
enhancements will follow in subsequent PRs.
references: #11602 , #11481
## Testing
- Configure a Voice inbox in Chatwoot with your Twilio number.
- Place a call to that number.
- Verify a new conversation appears in the Voice inbox for the call.
- Open it and confirm a dedicated voice call message bubble is shown.
- Watch status update live (ringing/answered); hang up and see it change
to completed in both the bubble and conversation
list.
- to test missed call status, make sure to hangup the call before the
please wait while we connect you to an agent message plays
## Screens
<img width="400" alt="Screenshot 2025-09-03 at 3 11 25 PM"
src="https://github.com/user-attachments/assets/d6a1d2ff-2ded-47b7-9144-a9d898beb380"
/>
<img width="700" alt="Screenshot 2025-09-03 at 3 11 33 PM"
src="https://github.com/user-attachments/assets/c25e6a1e-a885-47f7-b3d7-c3e15eef18c7"
/>
<img width="700" alt="Screenshot 2025-09-03 at 3 11 57 PM"
src="https://github.com/user-attachments/assets/29e7366d-b1d4-4add-a062-4646d2bff435"
/>
<img width="442" height="255" alt="Screenshot 2025-09-04 at 11 55 01 PM"
src="https://github.com/user-attachments/assets/703126f6-a448-49d9-9c02-daf3092cc7f9"
/>
---------
Co-authored-by: Muhsin <muhsinkeramam@gmail.com>
This PR adds the foundation for account-level SAML SSO configuration in
Chatwoot Enterprise. It introduces a new `AccountSamlSettings` model and
management API that allows accounts to configure their own SAML identity
providers independently, this also includes the certificate generation
flow
The implementation includes a new controller
(`Api::V1::Accounts::SamlSettingsController`) that provides CRUD
operations for SAML configuration
The feature is properly gated behind the 'saml' feature flag and
includes administrator-only authorization via Pundit policies.
We now support searching within the actual message content, email
subject lines, and audio transcriptions. This enables a faster, more
accurate search experience going forward. Unlike the standard message
search, which is limited to the last 3 months, this search has no time
restrictions.
The search engine also accounts for small variations in queries. Minor
spelling mistakes, such as searching for slck instead of Slack, will
still return the correct results. It also ignores differences in accents
and diacritics, so searching for Deja vu will match content containing
Déjà vu.
We can also refine searches in the future by criteria such as:
- Searching within a specific inbox
- Filtering by sender or recipient
- Limiting to messages sent by an agent
Fixes https://github.com/chatwoot/chatwoot/issues/11656
Fixes https://github.com/chatwoot/chatwoot/issues/10669
Fixes https://github.com/chatwoot/chatwoot/issues/5910
---
Rake tasks to reindex all the messages.
```sh
bundle exec rake search:all
```
Rake task to reindex messages from one account only
```sh
bundle exec rake search:account ACCOUNT_ID=1
```
## Linear reference:
https://linear.app/chatwoot/issue/CW-4649/re-imagine-assignments
## Description
This PR introduces the foundation for Assignment V2 system by
implementing agent_capacity and their association with inboxes and
users.
## Type of change
- [ ] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
Test Coverage:
- Controller specs for assignment policies CRUD operations
- Enterprise-specific specs for balanced assignment order
- Model specs for community/enterprise separation
## Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
---------
Co-authored-by: Pranav <pranav@chatwoot.com>
This PR adds the ability to modify the embedding model used by Captain
AI.Previously, the embedding model was hardcoded which led to errors when
you used a different API provider which did not support that specific
embedding model.
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
There were customer reported issues with FAQs which were generated in a
different langauge than what they were expecting. The reason behind this
was that the language of the account was not considered in the prompt
provided. If the language of the content was say Spanish, and the
account locale was english. The output was not predicable. The output
depends on the model and the execution time.
This PR would update the prompt to behave consistently with the account
locale. Even though the content provided is in a different language, it
would generate FAQs in the account locale.
Changes:
- Updated the prompt to include a detailed expectation of the FAQs
quality along with the language
- Added specs for the services where the prompt generator is called.
Tested the prompt using Phoenix playground across GPT 5, GPT 4.1, GPT
4.0. The reasoning setting for GPT 5 needs to be low so that it doesn't
generate random questions like "What was this updated?"
This PR improves the voice call creation flow by simplifying
configuration and automating setup with Twilio APIs.
references: #11602 , #11481
## Key changes
- Removed the requirement for twiml_app_sid – provisioning is now
automated through APIs.
- Auto-configured webhook URLs for:
- Voice number callbacks
- Status callbacks
- twiML callbacks
- Disabled business hours, help center, and related options until voice
inbox is fully supported.
- Added a configuration tab in the voice inbox to display the required
Twilio URLs (to make verification easier in Twilio console).
## Test Cases
- Provisioning
- Create a new voice inbox → verify that Twilio app provisioning happens
automatically.
- Verify twiML callback
- Webhook configuration
- Check that both voice number callback and status callback URLs are
auto-populated in Twilio.
- Disabled features
- Confirm that business hours and help center options are
hidden/disabled for voice inbox.
- Configuration tab
- Open the voice inbox configuration tab → verify that the displayed
Twilio URLs match what’s set in Twilio.
## Linear reference:
https://linear.app/chatwoot/issue/CW-4649/re-imagine-assignments
## Description
This PR introduces the foundation for Assignment V2 system by
implementing assignment policies and their association with inboxes.
Assignment policies allow configuring how conversations are distributed
among agents, with support for different assignment orders (round_robin
in community, balanced in enterprise) and conversation prioritization
strategies
Fixes # (issue)
## Type of change
Please delete options that are not relevant.
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality not to work as expected)
- [ ] This change requires a documentation update
## How Has This Been Tested?
Test Coverage:
- Controller specs for assignment policies CRUD operations
- Enterprise-specific specs for balanced assignment order
- Model specs for community/enterprise separation
Manual Testing:
1. Create assignment policy: POST
/api/v1/accounts/{id}/assignment_policies
2. List policies: GET /api/v1/accounts/{id}/assignment_policies
3. Assign policy to inbox: POST
/api/v1/accounts/{id}/assignment_policies/{id}/inboxes
4. View inbox policy: GET
/api/v1/accounts/{id}/inboxes/{id}/assignment_policy
5. Verify community edition ignores "balanced" assignment order
6. Verify enterprise edition supports both "round_robin" and "balanced"
- testing the flows after enterprise folder deletion
## Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
---------
Co-authored-by: Pranav <pranavrajs@gmail.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
# Pull Request Template
## Description
Fixes
[CW-4620](https://linear.app/chatwoot/issue/CW-4620/rethinking-custom-domains-in-chatwoot)
<img width="642" height="187" alt="Screenshot 2025-07-29 at 8 17 44 PM"
src="https://github.com/user-attachments/assets/ad2f5dac-4b27-4dce-93ca-6cbba74443fb"
/>
## Type of change
- [x] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
---------
Co-authored-by: Vishnu Narayanan <iamwishnu@gmail.com>
Co-authored-by: Pranav <pranavrajs@gmail.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
This PR migrates the legacy OpenAI integration (where users provide
their own API keys) from using hardcoded `https://api.openai.com`
endpoints to use the configurable `CAPTAIN_OPEN_AI_ENDPOINT` from the
captain configuration. This ensures consistency across all OpenAI
integrations in the platform.
## Changes
- Updated `lib/integrations/openai_base_service.rb` to use captain
endpoint config
- Updated `enterprise/app/models/enterprise/concerns/article.rb` to use
captain endpoint config
- Removed unused `enterprise/lib/chat_gpt.rb` class
- Added tests for endpoint configuration behavior
This PR adds support for automatic SSL issuance using Cloudflare when a
custom domain is updated.
- Introduced a cloudflare configuration. If present, the system will
attempt to issue an SSL certificate via Cloudflare whenever a custom
domain is added or changed.
- SSL verification is handled using an HTTP challenge.
- The job will store the HTTP challenge response provided by Cloudflare
and serve it under the /.well-known/cf path automatically.
How to test:
- Create a Cloudflare zone for your domain and copy the Zone ID.
- Generate a Cloudflare API token with the required SSL certificate
permissions.
- Set the Fallback Origin under SSL -> Custom HostName to the Chatwoot
installation.
- Add or update a custom domain and verify that the SSL certificate is
automatically issued.
---------
Co-authored-by: Sojan Jose <sojan@pepalo.com>
## Description
This PR introduces WhatsApp Embedded Signup functionality, enabling
users to connect their WhatsApp Business accounts through Meta's
streamlined OAuth flow without manual webhook configuration. This
significantly improves the user experience by automating the entire
setup process.
**Key Features:**
- Embedded signup flow using Facebook SDK and Meta's OAuth 2.0
- Automatic webhook registration and phone number configuration
- Enhanced provider selection UI with card-based design
- Real-time progress tracking during signup process
- Comprehensive error handling and user feedback
## Required Configuration
The following environment variables must be configured by administrators
before this feature can be used:
Super Admin Configuration (via
super_admin/app_config?config=whatsapp_embedded)
- `WHATSAPP_APP_ID`: The Facebook App ID for WhatsApp Business API
integration
- `WHATSAPP_CONFIGURATION_ID`: The Configuration ID for WhatsApp
Embedded Signup flow (obtained from Meta Developer Portal)
- `WHATSAPP_APP_SECRET`: The App Secret for WhatsApp Embedded Signup
flow (required for token exchange)

## How Has This Been Tested?
#### Backend Tests (RSpec):
- Authentication validation for embedded signup endpoints
- Authorization code validation and error handling
- Missing business parameter validation
- Proper response format for configuration endpoint
- Unauthorized access prevention
#### Manual Test Cases:
- Complete embedded signup flow (happy path)
- Provider selection UI navigation
- Facebook authentication popup handling
- Error scenarios (cancelled auth, invalid business data, API failures)
- Configuration presence/absence behavior
## Related Screenshots:





Fixes
https://linear.app/chatwoot/issue/CW-2131/spec-for-whatsapp-cloud-channels-sign-in-with-facebook
---------
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
Disable features/show billing upgrade for accounts with more users than
the one in the license.
---------
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
# Pull Request Template
## Description
The `auto_assignment` max_limits were being enforced even if the inbox
level `auto_assign` feature was disabled. This was because the
enterprise method was not verifying the feature status before returning
the available agents.
## Type of change
Please delete options that are not relevant.
- [x] Bug fix (non-breaking change which fixes an issue)
## How Has This Been Tested?
- Tested locally
- Added Specs
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules
# Pull Request Template
## Linear links:
-
https://linear.app/chatwoot/issue/CW-4479/if-image-is-sent-by-the-customer-send-it-to-openai
## Description
This pull request adds “Captain image support” to Chatwoot. It
introduces multimodal message handling so that when a customer sends an
image, Captain can forward the file to OpenAI’s vision endpoint,
generate a caption/analysis
## Type of change
Please delete options that are not relevant.
- [x] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
<img width="891" alt="image"
src="https://github.com/user-attachments/assets/c7cc98ed-cc44-4865-a53a-83d129e2fe2c"
/>
## Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
---------
Co-authored-by: Pranav <pranav@chatwoot.com>
This PR introduces a new channel type for voice conversations.
ref: #11481
## Changes
- Add database migration for channel_voice table with phone_number and
provider_config
- Create Channel::Voice model with E.164 phone number validation and
Twilio config validation
- Add voice channel association to Account model
- Extend inbox helpers and types to support voice channels
- Add voice channel setup UI with Twilio configuration form
- Include voice channel in channel factory and list components
- Add API routes and store actions for voice channel creation
- Add comprehensive translations for voice channel management
---------
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
# Pull Request Template
## Linear task:
https://linear.app/chatwoot/issue/CW-4482/captain-should-be-able-to-access-private-notes-only-on-copilot
## Description
Captain should be able to access private notes (only on copilot)
## Type of change
- [x] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?


## Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
---------
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
<img width="1240" alt="Screenshot 2025-06-05 at 12 39 04 AM"
src="https://github.com/user-attachments/assets/0071cd23-38c3-4638-946e-f1fbd11ec845"
/>
## Changes
Give the admins an option to delete conversation via the context menu
- enable conversation deletion in routes and controller
- expose delete API on conversations
- add delete option in conversation context menu and integrate with card
and list
- implement store action and mutation for delete
- update i18n with new strings
fixes: https://github.com/chatwoot/chatwoot/issues/947
---------
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Pranav <pranavrajs@gmail.com>
- Extended CsatSurveyResponsePolicy to support report_manage permission
- Added enterprise extension module following existing pattern
- Users with report_manage custom role can now access CSAT index,
metrics, and download
- Added comprehensive tests for both base and enterprise policy behavior
- Enable jobs by default when a copilot thread or a message is created.
- Rename thread_id to copilot_thread_id to keep it consistent with the
model name
- Add a spec for search_linear_issues service
- Automate the deletion of accounts that have requested deletion via
account settings.
- Add a Sidekiq job that runs daily to find accounts that have requested
deletion and have passed the 7-day window.
- This job deletes the account and then soft-deletes users if they do
not belong to any other account.
- This job also sends an email to the Chatwoot instance admin for
compliance purposes.
- The Chatwoot instance admin's email is configurable via the
`CHATWOOT_INSTANCE_ADMIN_EMAIL` global config.
---------
Co-authored-by: Sojan Jose <sojan@pepalo.com>
- Add API support for creating a thread
- Add API support for creating a message
- Remove uuid from thread (no longer required, we will use existing
websocket connection to send messages)
- Update message_type to a column (user, assistant, assistant_thinking)
Earlier, we were manually checking if a user was an agent and filtering
their conversations based on inboxes. This logic should have been part
of the conversation permissions service.
This PR moves the check to the right place and updates the logic
accordingly.
Other updates:
- Add support for search_conversations service for copilot.
- Use PermissionFilterService in contacts/conversations, conversations,
copilot search_conversations.
---------
Co-authored-by: Sojan <sojan@pepalo.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This PR adds a tool to search Linear issues. If the integration is
enabled for the account, the tool will return results as expected. Also
introduces support for an `active?` method, which allows third-party
Copilot tools to be conditionally enabled based on the status of the
integration on the account.
This PR introduces the concept of a tool registry. The implementation is
straightforward: you can define a tool by creating a class with a
function name. The function name gets registered in the registry and can
be referenced during LLM calls. When the LLM invokes a tool using the
registered name, the registry locates and executes the appropriate tool.
If the LLM calls an unregistered tool, the registry returns an error
indicating that the tool is not defined.
Show captain messages under the name of the assistant which generated
the message.
- Add support for `Captain::Assistant` sender type
- Add push_event_data for captain_assistants
- Add activity message handler for captain_assistants
- Update UI to show captain messages under the name of the assistant
- Fix the issue where openAI errors when image is sent
- Add support for custom name of the assistant
---------
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
The agents can see the previous conversations with the copilot if needed
with this change. We would have to cleanup the data after a while. For
now, that is not considered.
This PR adds:
- A new model for copilot_threads (intentionally named thread instead of
conversation to avoid confusion), copilot_messages
- Add the controller to fetch previous threads and messages.
- Refactor HandleStripeEventService to better manage features by plan
- Add constants for features available in each plan tier (Startup,
Business, Enterprise)
- Add channel_instagram to Startup plan features
- Improve downgrade handling to properly disable higher-tier features
- Clean up and optimize tests for maintainability
- Add comprehensive test coverage for plan upgrades and downgrades
---------
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
# Pull Request Template
## Description
Please include a summary of the change and issue(s) fixed. Also, mention
relevant motivation, context, and any dependencies that this change
requires.
Fixes # (issue)
## Type of change
Please delete options that are not relevant.
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality not to work as expected)
- [ ] This change requires a documentation update
## How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide
instructions so we can reproduce. Please also list any relevant details
for your test configuration.
## Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
This PR introduces basic minimum version of **Instagram Business
Login**, making Instagram inbox setup more straightforward by removing
the Facebook Page dependency. This update enhances user experience and
aligns with Meta’s recommended best practices.
Fixes
https://linear.app/chatwoot/issue/CW-3728/instagram-login-how-to-implement-the-changes
## Why Introduce Instagram as a Separate Inbox?
Currently, our Instagram integration requires linking an Instagram
account to a Facebook Page, making setup complex. To simplify this
process, Instagram now offers **Instagram Business Login**, which allows
users to authenticate directly with their Instagram credentials.
The **Instagram API with Instagram Login** enables businesses and
creators to send and receive messages without needing a Facebook Page
connection. While an Instagram Business or Creator account is still
required, this approach provides a more straightforward integration
process.
| **Existing Approach (Facebook Login for Business)** | **New Approach
(Instagram Business Login)** |
| --- | --- |
| Requires linking Instagram to a Facebook Page | No Facebook Page
required |
| Users log in via Facebook credentials | Users log in via Instagram
credentials |
| Configuration is more complex | Simpler setup |
Meta recommends using **Instagram Business Login** as the preferred
authentication method due to its easier configuration and improved
developer experience.
---
## Implementation Plan
The core messaging functionality is already in place, but the transition
to **Instagram Business Login** requires adjustments.
### Changes & Considerations
- **API Adjustments**: The Instagram API uses `graph.instagram`, whereas
Koala (our existing library) interacts with `graph.facebook`. We may
need to modify API calls accordingly.
- **Three Main Modules**:
1. **Instagram Business Login** – Handle authentication flow.
2. **Permissions & Features** – Ensure necessary API scopes are granted.
3. **Webhooks** – Enable real-time message retrieval.

---
## Instagram Login Flow
1. User clicks **"Create Inbox"** for Instagram.
2. App redirects to the [Instagram Authorization
URL](https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/business-login#embed-the-business-login-url).
3. After authentication, Instagram returns an authorization code.
5. The app exchanges the code for a **long-lived token** (valid for 60
days).
6. Tokens are refreshed periodically to maintain access.
7. Once completed, the app creates an inbox and redirects to the
Chatwoot dashboard.
---
## How to Test the Instagram Inbox
1. Create a new app on [Meta's Developer
Portal](https://developers.facebook.com/apps/).
2. Select **Business** as the app type and configure it.
3. Add the Instagram product and connect a business account.
4. Copy Instagram app ID and Instagram app secret
5. Add the Instagram app ID and Instagram app secret to your app config
via `{Chatwoot installation
url}/super_admin/app_config?config=instagram`
6. Configure Webhooks:
- Callback URL: `{your_chatwoot_url}/webhooks/instagram`
- Verify Token: `INSTAGRAM_VERIFY_TOKEN`
- Subscribe to `messages`, `messaging_seen`, and `message_reactions`
events.
7. Set up **Instagram Business Login**:
- Redirect URL: `{your_chatwoot_url}/instagram/callback`
8. Test inbox creation via the Chatwoot dashboard.
## Troubleshooting & Common Errors
### Insufficient Developer Role Error
- Ensure the Instagram user is added as a developer:
- **Meta Dashboard → App Roles → Roles → Add People → Enter Instagram
ID**
### API Access Deactivated
- Ensure the **Privacy Policy URL** is valid and correctly set.
### Invalid request: Request parameters are invalid: Invalid
redirect_uri
- Please configure the Frontend URL. The Frontend URL does not match the
authorization URL.
---
## To-Do List
- [x] Basic integration setup completed.
- [x] Enable sending messages via [Messaging
API](https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/messaging-api).
- [x] Implement automatic webhook subscriptions on inbox creation.
- [x] Handle **canceled authorization errors**.
- [x] Handle all the errors
https://developers.facebook.com/docs/instagram-platform/instagram-graph-api/reference/error-codes
- [x] Dynamically fetch **account IDs** instead of hardcoding them.
- [x] Prevent duplicate Instagram channel creation for the same account.
- [x] Use **Global Config** instead of environment variables.
- [x] Explore **Human Agent feature** for message handling.
- [x] Write and refine **test cases** for all scenarios.
- [x] Implement **token refresh mechanism** (tokens expire after 60
days).
Fixes https://github.com/chatwoot/chatwoot/issues/10440
---------
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
* chore: lint files
* chore: suppress warning
* chore: disable suggest extensions
* chore: do not stage changes in pre-commit
* chore: remove git add from FE lint and `-a` flag from rubocop on husky
- Removes the portal_members table and all associated records
- Updates policies to use custom roles with knowledge_base_manage
permission
- Updates controllers, models, and views to work without portal
membership
- Adds tests for the new permission model
## Description
Add account delete option in the user account settings.
Fixes#1555
## Type of change
- [ ] New feature (non-breaking change which adds functionality)


## Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
---------
Co-authored-by: Sojan Jose <sojan@pepalo.com>
Co-authored-by: Sojan Jose <sojan.official@gmail.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
1. Add permission filter service to separate permission filtering logic
from conversation queries
2. Implement hierarchical permissions with cleaner logic:
- conversation_manage gives access to all conversations
- conversation_unassigned_manage gives access to unassigned and user's
conversations
- conversation_participating_manage gives access only to user's
conversations
---------
Co-authored-by: Pranav <pranav@chatwoot.com>
# Pull Request Template
## Description
This PR will replace the upgrade banner with an upgrade page view.
## Type of change
- [x] Bug fix (non-breaking change which fixes an issue)
## How Has This Been Tested?
### Loom video
https://www.loom.com/share/0f2b4b09acdd4404bf3211184a470227?sid=7ed60a99-0299-4642-b907-2af8c4dcc643
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
---------
Co-authored-by: Pranav <pranavrajs@gmail.com>
This PR implements the following features
- FAQs from conversations will be generated in account language
- Contact notes will be generated in account language
- Copilot chat will respond in user language, unless the agent asks the
question in a different language
## Changes
### Copilot Chat
- Update the prompt to include an instruction for the language, the bot
will reply in asked language, but will default to account language
- Update the `ChatService` class to include pass the language to
`SystemPromptsService`
### FAQ and Contact note generation
- Update contact note generator and conversation generator to include
account locale
- Pass the account locale to `SystemPromptsService`
<details><summary>Screenshots</summary>
#### FAQs being generated in system langauge

#### Copilot responding in system language

</details>
---------
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
This PR adds native integration with Shopify. No more dashboard apps.
The support agents can view the orders, their status and the link to the
order page on the conversation sidebar.
This PR does the following:
- Create an integration with Shopify (a new app is added in the
integrations tab)
- Option to configure it in SuperAdmin
- OAuth endpoint and the callbacks.
- Frontend component to render the orders. (We might need to cache it in
the future)
---------
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
- Add a warning logger for cases where we are getting webhook events for
inactive numbers.
- Add config to discard events for inactive numbers so that the meta
will stop sending events
---------
Co-authored-by: Pranav <pranav@chatwoot.com>
This PR adds service to automate account abuse detection. Currently
based on the signup name and URL, could potentially add more context
such as usage analysis, message metadata etc.
This PR allows Copilot to be used without connecting the Captain assistant to an inbox. Currently, if Captain is enabled on an account, it takes over conversations and responds directly to users. This PR enables the use of Captain as a Copilot without allowing it to respond to users. Additionally, it allows using a different assistant for Copilot instead of the default Captain assistant.
The selection logic for the Copilot assistant follows this order of preference:
- If the user has selected a specific assistant, it takes first preference for Copilot.
- If the above is not available, the assistant connected to the inbox takes preference.
- If neither of the above is available, the first assistant in the account takes preference.
This PR has the following changes
- Add `LINEAR_CLIENT_ID` and `LINEAR_CLIENT_SECRET` to installation
config
- Add Linear config to super_admin/features.yml
- Replace usage of ENV.fetch with GlobalConfigService.load for fetch
Client Id and Secret.
This PR ensures that only conversations from quick conversation channels
are resolved, avoiding resolutions on the email channel (we still need
to improve the UX here). It also updates the FAQ generation logic,
limiting it to conversations that had at least one human interaction.
This PR introduces several improvements to the Captain AI dashboard
section:
- New billing page, with new colors, layout and meters for Captain usage
- Updated the base paywall component to use new colors
- Updated PageLayout.vue, it's more generic and can be used for other
pages as well
- Use flags to toggle empty state and loading state
- Add prop for `featureFlag` to show the paywall slot based on feature
enabled on account
- Update `useAccount` to add a `isCloudFeatureEnabled`
- **Removed feature flag checks from captain route definitions**, so the
captain entry will always be visible on the sidebar
- Add banner to Captain pages for the following cases
- Responses usage is over 80%
- Documents limit is fully exhausted
### Screenshots
<details><summary>Free plan</summary>
<p>


</p>
</details>
<details><summary>Paid plan</summary>
<p>


</p>
</details>
---------
Co-authored-by: Sojan Jose <sojan@pepalo.com>
Co-authored-by: Pranav <pranav@chatwoot.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
- Fixed Firecrawl webhook payloads to ensure proper data handling and
delivery.
- Removed unused Robin AI code to improve codebase cleanliness and
maintainability.
- Implement authentication for the Firecrawl endpoint to improve
security. A key is generated to secure the webhook URLs from FireCrawl.
---------
Co-authored-by: Pranav <pranavrajs@gmail.com>
This pull request introduces several changes to implement and manage
usage limits for the Captain AI service. The key changes include adding
configuration for plan limits, updating error messages, modifying
controllers and models to handle usage limits, and updating tests to
ensure the new functionality works correctly.
## Implementation Checklist
- [x] Ability to configure captain limits per check
- [x] Update response for `usage_limits` to include captain limits
- [x] Methods to increment or reset captain responses limits in the
`limits` column for the `Account` model
- [x] Check documents limit using a count query
- [x] Ensure Captain hand-off if a limit is reached
- [x] Ensure limits are enforced for Copilot Chat
- [x] Ensure limits are reset when stripe webhook comes in
- [x] Increment usage for FAQ generation and Contact notes
- [x] Ensure documents limit is enforced
These changes ensure that the Captain AI service operates within the defined usage limits for different subscription plans, providing appropriate error messages and handling when limits are exceeded.
Currently, it’s unclear whether an FAQ item is generated from a
document, derived from a conversation, or added manually.
This PR resolves the issue by providing visibility into the source of
each FAQ. Users can now see whether an FAQ was generated or manually
added and, if applicable, by whom.
- Move the document_id to a polymorphic relation (documentable).
- Updated the APIs to accommodate the change.
- Update the service to add corresponding references.
- Updated the specs.
<img width="1007" alt="Screenshot 2025-01-15 at 11 27 56 PM"
src="https://github.com/user-attachments/assets/7d58f798-19c0-4407-b3e2-748a919d14af"
/>
---------
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
This PR introduces a review step for generated FAQs, allowing a human to
validate and approve them before use in customer interactions. While
hallucinations are minimal, this step ensures accurate and reliable FAQs
for Captain to use during LLM calls when responding to customers.
- Added a status field for the FAQ
- Allow the filter on the UI.
<img width="1072" alt="Screenshot 2025-01-15 at 6 39 26 PM"
src="https://github.com/user-attachments/assets/81dfc038-31e9-40e6-8a09-586ebc4e8384"
/>
Migration Guide: https://chwt.app/v4/migration
This PR imports all the work related to Captain into the EE codebase. Captain represents the AI-based features in Chatwoot and includes the following key components:
- Assistant: An assistant has a persona, the product it would be trained on. At the moment, the data at which it is trained is from websites. Future integrations on Notion documents, PDF etc. This PR enables connecting an assistant to an inbox. The assistant would run the conversation every time before transferring it to an agent.
- Copilot for Agents: When an agent is supporting a customer, we will be able to offer additional help to lookup some data or fetch information from integrations etc via copilot.
- Conversation FAQ generator: When a conversation is resolved, the Captain integration would identify questions which were not in the knowledge base.
- CRM memory: Learns from the conversations and identifies important information about the contact.
---------
Co-authored-by: Vishnu Narayanan <vishnu@chatwoot.com>
Co-authored-by: Sojan <sojan@pepalo.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
In admin settings, this Pr will add the UI for managing custom roles (
ref: https://github.com/chatwoot/chatwoot/pull/9995 ). It also handles
the routing logic changes to accommodate fine-tuned permissions.
---------
Co-authored-by: Pranav <pranavrajs@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This PR allows setting scripts for `vueapp.html.erb` via super admin
config. This PR has the following changes
1. Allow `DASHBOARD_SCRIPTS` in internal config
2. Remove existing scripts from `vueapp.html.erb`
3. Add scripts from `GlobalConfig` to `vueapp.html.erb`
---------
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This PR has the following changes
1. Add `AZURE_APP_ID` and `AZURE_APP_SECRET` to installation config
2. Add Microsoft config to `super_admin/features.yml`
3. Replace usage of `ENV.fetch` with `GlobalConfigService.load` for
fetch App ID and Secret