From cf6ee136650d95c992b0894a502fa9606525d502 Mon Sep 17 00:00:00 2001 From: Vishnu Narayanan Date: Tue, 16 Jun 2026 18:56:30 +0530 Subject: [PATCH] feat: scale email rate limits by agent count for paid plans (#13966) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Paid plan accounts with many agents were hitting the flat daily email rate limit cap. This multiplies the plan base limit by the account's agent seat count, so larger teams get proportionally higher limits. Free/hacker plan keeps the flat limit unchanged. Fixes https://linear.app/chatwoot/issue/CW-6664 ## How to test 1. Set up `ACCOUNT_EMAILS_PLAN_LIMITS` config with plan limits (e.g., `{"hacker": 10, "startups": 20, "business": 30, "enterprise": 40}`) 2. Create an account on a paid plan (e.g., startups) with multiple agent seats 3. Verify `account.email_rate_limit` returns `base_limit × agent_seats` (e.g., 20 × 5 = 100) 4. Create an account on the hacker plan — verify limit stays flat (10) 5. Set a per-account override via super admin `limits.emails` — verify it takes priority over the multiplied limit ## What changed - `plan_email_limit` now multiplies the base plan limit by agent seat count for paid plans - Added `free_plan?` helper to skip the multiplier for the default/free plan --- .../enterprise/account/plan_usage_and_limits.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/enterprise/app/models/enterprise/account/plan_usage_and_limits.rb b/enterprise/app/models/enterprise/account/plan_usage_and_limits.rb index 0937c7b82..9b5093507 100644 --- a/enterprise/app/models/enterprise/account/plan_usage_and_limits.rb +++ b/enterprise/app/models/enterprise/account/plan_usage_and_limits.rb @@ -75,6 +75,14 @@ module Enterprise::Account::PlanUsageAndLimits # rubocop:disable Metrics/ModuleL end def plan_email_limit + base_limit = plan_base_email_limit + return nil if base_limit.nil? + return base_limit if free_plan? + + base_limit * [agent_limits.to_i, 1].max + end + + def plan_base_email_limit config = InstallationConfig.find_by(name: 'ACCOUNT_EMAILS_PLAN_LIMITS')&.value return nil if config.blank? || plan_name.blank? @@ -84,6 +92,11 @@ module Enterprise::Account::PlanUsageAndLimits # rubocop:disable Metrics/ModuleL nil end + def free_plan? + default_plan = InstallationConfig.find_by(name: 'CHATWOOT_CLOUD_PLANS')&.value&.first + default_plan.present? && plan_name&.downcase == default_plan['name']&.downcase + end + def default_captain_limits max_limits = { documents: ChatwootApp.max_limit, responses: ChatwootApp.max_limit }.with_indifferent_access zero_limits = { documents: 0, responses: 0 }.with_indifferent_access