From 50720f2395c61d383914bc73df0b38cedf982fac Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 20 Apr 2026 17:47:24 +0530 Subject: [PATCH] feat: Gate conversation continuity toggle behind inbound_emails feature flag (#13838) The "conversation continuity via email" toggle was visible to all accounts regardless of whether they had `inbound_emails` enabled. Without inbound email infrastructure, replies to those follow-up emails land in the agent's personal inbox instead of routing back into Chatwoot. The feature appears to work but silently breaks the reply path. The toggle is now gated on the `inbound_emails` feature flag. On self-hosted without the feature, the toggle is hidden entirely. On cloud, it remains visible but disabled with upgrade messaging. On the backend, `inbound_emails` is added to the manually managed features list in `InternalAttributesService` so that Stripe webhook plan syncs don't override it when support enables it for an account. --------- Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> --- .../dashboard/i18n/locale/en/inboxMgmt.json | 1 + .../dashboard/settings/inbox/Settings.vue | 37 ++++++++++++++++--- .../accounts/internal_attributes_service.rb | 4 +- .../_form.html.erb | 5 +-- 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json index 51f855689..6b1ff20b1 100644 --- a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json @@ -745,6 +745,7 @@ "SENDER_NAME_SECTION_TEXT": "Enable/Disable showing Agent's name in email, if disabled it will show business name", "ENABLE_CONTINUITY_VIA_EMAIL": "Enable conversation continuity via email", "ENABLE_CONTINUITY_VIA_EMAIL_SUB_TEXT": "Conversations will continue over email if the contact email address is available.", + "ENABLE_CONTINUITY_VIA_EMAIL_DISABLED_TEXT": "This feature is available on a paid plan. Upgrade to enable conversation continuity via email.", "LOCK_TO_SINGLE_CONVERSATION": "Conversation Routing", "LOCK_TO_SINGLE_CONVERSATION_SUB_TEXT": "Configure conversation creation for existing contacts", "INBOX_UPDATE_TITLE": "Inbox Settings", diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue index c059c57f1..11eb1f6aa 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue @@ -112,9 +112,33 @@ export default { ...mapGetters({ accountId: 'getCurrentAccountId', isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount', + isOnChatwootCloud: 'globalConfig/isOnChatwootCloud', uiFlags: 'inboxes/getUIFlags', portals: 'portals/allPortals', }), + isInboundEmailEnabled() { + return this.isFeatureEnabledonAccount( + this.accountId, + FEATURE_FLAGS.INBOUND_EMAILS + ); + }, + showContinuityToggle() { + if (this.isInboundEmailEnabled) return true; + return this.isOnChatwootCloud; + }, + isContinuityDisabled() { + return this.isOnChatwootCloud && !this.isInboundEmailEnabled; + }, + continuityDescription() { + if (this.isContinuityDisabled) { + return this.$t( + 'INBOX_MGMT.SETTINGS_POPUP.ENABLE_CONTINUITY_VIA_EMAIL_DISABLED_TEXT' + ); + } + return this.$t( + 'INBOX_MGMT.SETTINGS_POPUP.ENABLE_CONTINUITY_VIA_EMAIL_SUB_TEXT' + ); + }, selectedTabKey() { return this.tabs[this.selectedTabIndex]?.key; }, @@ -542,7 +566,8 @@ export default { welcome_tagline: this.channelWelcomeTagline || '', selectedFeatureFlags: this.selectedFeatureFlags, reply_time: this.replyTime || 'in_a_few_minutes', - continuity_via_email: this.continuityViaEmail, + continuity_via_email: + this.isInboundEmailEnabled && this.continuityViaEmail, }, }; if (this.avatarFile) { @@ -1148,15 +1173,15 @@ export default { /> diff --git a/enterprise/app/services/internal/accounts/internal_attributes_service.rb b/enterprise/app/services/internal/accounts/internal_attributes_service.rb index 116d0a3fc..d119d6345 100644 --- a/enterprise/app/services/internal/accounts/internal_attributes_service.rb +++ b/enterprise/app/services/internal/accounts/internal_attributes_service.rb @@ -52,9 +52,9 @@ class Internal::Accounts::InternalAttributesService # Get list of valid features that can be manually managed def valid_feature_list - # Business and Enterprise plan features only Enterprise::Billing::ReconcilePlanFeaturesService::BUSINESS_PLAN_FEATURES + - Enterprise::Billing::ReconcilePlanFeaturesService::ENTERPRISE_PLAN_FEATURES + Enterprise::Billing::ReconcilePlanFeaturesService::ENTERPRISE_PLAN_FEATURES + + %w[inbound_emails] end # Account notes functionality removed for now diff --git a/enterprise/app/views/fields/manually_managed_features_field/_form.html.erb b/enterprise/app/views/fields/manually_managed_features_field/_form.html.erb index 97b21090a..a4a18a9b9 100644 --- a/enterprise/app/views/fields/manually_managed_features_field/_form.html.erb +++ b/enterprise/app/views/fields/manually_managed_features_field/_form.html.erb @@ -2,9 +2,8 @@ # Get all feature names and their display names all_feature_display_names = SuperAdmin::AccountFeaturesHelper.feature_display_names - # Business and Enterprise plan features only - premium_features = Enterprise::Billing::HandleStripeEventService::BUSINESS_PLAN_FEATURES + - Enterprise::Billing::HandleStripeEventService::ENTERPRISE_PLAN_FEATURES + # Features that can be manually managed + premium_features = Internal::Accounts::InternalAttributesService.new(field.resource).valid_feature_list # Get only premium features with display names premium_features_with_display = premium_features.map do |feature|