From 7d9f9605a22e9d3b03ba4426a3637faac05fc45c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Thu, 28 May 2026 13:22:55 +0200 Subject: [PATCH] feat(settings): move email handles and emailing domains to dedicated Email page (#21008) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Both **Email Handles** and **Emailing Domains** were rendered on the General workspace settings page, but they're workspace-level *email infrastructure* (inbound shared addresses + outbound sender authentication) and don't belong with the workspace name, picture, and domain config. - New `SettingsWorkspaceEmail` page at `/settings/email` - Nav item under **Workspace**, hidden when `IS_EMAIL_GROUP_ENABLED` is off (and gated by `WORKSPACE` permission) - Related sub-routes (`email-group/:messageChannelId`, `emailing-domain/:domainId`, etc.) moved from `general/` to `email/` so the URL space stays consistent with the page - General page now only contains name, picture, workspace domain, and the delete-workspace section No behavior changes to the underlying section components — they're imported as-is into the new page. ## Test plan - [ ] With `IS_EMAIL_GROUP_ENABLED` enabled: **Email** appears in the Workspace nav and the page renders both sections - [ ] With the flag disabled: **Email** is hidden from nav; navigating to `/settings/email` directly renders nothing - [ ] General page no longer shows Email Handles / Emailing Domains - [ ] Clicking a shared inbox row navigates to `/settings/email/email-group/:id` (was `general/...`) - [ ] "Add emailing domain" navigates to `/settings/email/emailing-domain/new` ## Notes - Pre-existing `twenty-front` typecheck error in `FrontComponentRendererProvider.tsx` (React types mismatch between sibling packages) reproduces on `main` and is unrelated to this PR. --- .../modules/app/components/SettingsRoutes.tsx | 10 +++++ .../SettingsAccountsNewEmailGroupChannel.tsx | 6 +-- .../hooks/useSettingsNavigationItems.tsx | 14 +++++- .../src/pages/settings/SettingsWorkspace.tsx | 18 +------- .../pages/settings/SettingsWorkspaceEmail.tsx | 44 +++++++++++++++++++ .../SettingsEmailingDomainDetail.tsx | 6 +-- .../SettingsNewEmailingDomain.tsx | 6 +-- ...ttingsWorkspaceEmailGroupChannelDetail.tsx | 6 +-- .../twenty-shared/src/types/SettingsPath.ts | 9 ++-- 9 files changed, 85 insertions(+), 34 deletions(-) create mode 100644 packages/twenty-front/src/pages/settings/SettingsWorkspaceEmail.tsx diff --git a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx index 98c0607f4a..cf7cc1b22d 100644 --- a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx +++ b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx @@ -128,6 +128,12 @@ const SettingsWorkspace = lazy(() => })), ); +const SettingsWorkspaceEmail = lazy(() => + import('~/pages/settings/SettingsWorkspaceEmail').then((module) => ({ + default: module.SettingsWorkspaceEmail, + })), +); + const SettingsWorkspaceEmailGroupChannelDetail = lazy(() => import('~/pages/settings/workspace/SettingsWorkspaceEmailGroupChannelDetail').then( (module) => ({ @@ -636,6 +642,10 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => ( } > } /> + } + /> } diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsNewEmailGroupChannel.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsNewEmailGroupChannel.tsx index 4159a61b30..fa07c2abc0 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsNewEmailGroupChannel.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsNewEmailGroupChannel.tsx @@ -53,8 +53,8 @@ export const SettingsAccountsNewEmailGroupChannel = () => { href: getSettingsPath(SettingsPath.Workspace), }, { - children: t`General`, - href: getSettingsPath(SettingsPath.Workspace), + children: t`Email`, + href: getSettingsPath(SettingsPath.WorkspaceEmail), }, { children: t`New Email Handle` }, ]} @@ -63,7 +63,7 @@ export const SettingsAccountsNewEmailGroupChannel = () => { isSaveDisabled={!canSave} isCancelDisabled={loading} isLoading={loading} - onCancel={() => navigate(SettingsPath.Workspace)} + onCancel={() => navigate(SettingsPath.WorkspaceEmail)} onSave={handleSave} /> } diff --git a/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx b/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx index 4a23f88b9f..6e8c9f4968 100644 --- a/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx +++ b/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx @@ -1,4 +1,4 @@ -import { SettingsPath } from 'twenty-shared/types'; +import { FeatureFlagKey, SettingsPath } from 'twenty-shared/types'; import { useAuth } from '@/auth/hooks/useAuth'; import { currentUserState } from '@/auth/states/currentUserState'; @@ -12,6 +12,7 @@ import { type NavigationDrawerItemModifier, } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { t } from '@lingui/core/macro'; import { isNonEmptyString } from '@sniptt/guards'; import { @@ -73,6 +74,9 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => { isNonEmptyString(supportChat.supportFrontChatId); const permissionMap = usePermissionFlagMap(); + const isEmailGroupFeatureEnabled = useIsFeatureEnabled( + FeatureFlagKey.IS_EMAIL_GROUP_ENABLED, + ); return [ { label: t`User`, @@ -120,6 +124,14 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => { Icon: IconSettings, isHidden: !permissionMap[PermissionFlagType.WORKSPACE], }, + { + label: t`Email`, + path: SettingsPath.WorkspaceEmail, + Icon: IconMail, + isHidden: + !isEmailGroupFeatureEnabled || + !permissionMap[PermissionFlagType.WORKSPACE], + }, { label: t`Data model`, path: SettingsPath.Objects, diff --git a/packages/twenty-front/src/pages/settings/SettingsWorkspace.tsx b/packages/twenty-front/src/pages/settings/SettingsWorkspace.tsx index 89d393519e..81f8680426 100644 --- a/packages/twenty-front/src/pages/settings/SettingsWorkspace.tsx +++ b/packages/twenty-front/src/pages/settings/SettingsWorkspace.tsx @@ -1,18 +1,14 @@ import { useLingui } from '@lingui/react/macro'; -import { isEmailGroupEnabledState } from '@/client-config/states/isEmailGroupEnabledState'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SettingsWorkspaceDomainCard } from '@/settings/domains/components/SettingsWorkspaceDomainCard'; import { DeleteWorkspace } from '@/settings/profile/components/DeleteWorkspace'; -import { SettingsWorkspaceEmailGroupSection } from '@/settings/workspace/components/SettingsWorkspaceEmailGroupSection'; -import { SettingsWorkspaceEmailingDomainsSection } from '@/settings/workspace/components/SettingsWorkspaceEmailingDomainsSection'; import { NameField } from '@/settings/workspace/components/NameField'; import { WorkspaceLogoUploader } from '@/settings/workspace/components/WorkspaceLogoUploader'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWorkspaceEnabledState'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; -import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; -import { FeatureFlagKey, SettingsPath } from 'twenty-shared/types'; +import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath } from 'twenty-shared/utils'; import { H2Title } from 'twenty-ui/display'; import { Section } from 'twenty-ui/layout'; @@ -24,14 +20,6 @@ export const SettingsWorkspace = () => { isMultiWorkspaceEnabledState, ); - const isEmailGroupEnabled = useAtomStateValue(isEmailGroupEnabledState); - const isEmailGroupFeatureEnabled = useIsFeatureEnabled( - FeatureFlagKey.IS_EMAIL_GROUP_ENABLED, - ); - const showEmailGroupSection = - isEmailGroupEnabled && isEmailGroupFeatureEnabled; - const showEmailingDomainsSection = isEmailGroupFeatureEnabled; - return ( { )} - {showEmailGroupSection && } - {showEmailingDomainsSection && ( - - )}
diff --git a/packages/twenty-front/src/pages/settings/SettingsWorkspaceEmail.tsx b/packages/twenty-front/src/pages/settings/SettingsWorkspaceEmail.tsx new file mode 100644 index 0000000000..d2b5d9bccc --- /dev/null +++ b/packages/twenty-front/src/pages/settings/SettingsWorkspaceEmail.tsx @@ -0,0 +1,44 @@ +import { useLingui } from '@lingui/react/macro'; + +import { isEmailGroupEnabledState } from '@/client-config/states/isEmailGroupEnabledState'; +import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; +import { SettingsWorkspaceEmailGroupSection } from '@/settings/workspace/components/SettingsWorkspaceEmailGroupSection'; +import { SettingsWorkspaceEmailingDomainsSection } from '@/settings/workspace/components/SettingsWorkspaceEmailingDomainsSection'; +import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; +import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { FeatureFlagKey, SettingsPath } from 'twenty-shared/types'; +import { getSettingsPath } from 'twenty-shared/utils'; + +export const SettingsWorkspaceEmail = () => { + const { t } = useLingui(); + + const isEmailGroupEnabled = useAtomStateValue(isEmailGroupEnabledState); + const isEmailGroupFeatureEnabled = useIsFeatureEnabled( + FeatureFlagKey.IS_EMAIL_GROUP_ENABLED, + ); + + if (!isEmailGroupFeatureEnabled) { + return null; + } + + const showEmailGroupSection = isEmailGroupEnabled; + + return ( + + + {showEmailGroupSection && } + + + + ); +}; diff --git a/packages/twenty-front/src/pages/settings/emailing-domains/SettingsEmailingDomainDetail.tsx b/packages/twenty-front/src/pages/settings/emailing-domains/SettingsEmailingDomainDetail.tsx index ca2cce2841..06beff5185 100644 --- a/packages/twenty-front/src/pages/settings/emailing-domains/SettingsEmailingDomainDetail.tsx +++ b/packages/twenty-front/src/pages/settings/emailing-domains/SettingsEmailingDomainDetail.tsx @@ -64,7 +64,7 @@ export const SettingsEmailingDomainDetail = () => { enqueueSuccessSnackBar({ message: t`Emailing domain deleted successfully`, }); - navigateSettings(SettingsPath.Workspace); + navigateSettings(SettingsPath.WorkspaceEmail); } catch (deleteError) { enqueueErrorSnackBar({ ...(CombinedGraphQLErrors.is(deleteError) @@ -83,8 +83,8 @@ export const SettingsEmailingDomainDetail = () => { href: getSettingsPath(SettingsPath.Workspace), }, { - children: Emailing Domains, - href: getSettingsPath(SettingsPath.Workspace), + children: Email, + href: getSettingsPath(SettingsPath.WorkspaceEmail), }, { children: emailingDomain.domain }, ]} diff --git a/packages/twenty-front/src/pages/settings/emailing-domains/SettingsNewEmailingDomain.tsx b/packages/twenty-front/src/pages/settings/emailing-domains/SettingsNewEmailingDomain.tsx index fb9f892be8..b29d8e1c08 100644 --- a/packages/twenty-front/src/pages/settings/emailing-domains/SettingsNewEmailingDomain.tsx +++ b/packages/twenty-front/src/pages/settings/emailing-domains/SettingsNewEmailingDomain.tsx @@ -114,7 +114,7 @@ export const SettingsNewEmailingDomain = () => { title={t`New Emailing Domain`} actionButton={ navigate(SettingsPath.Workspace)} + onCancel={() => navigate(SettingsPath.WorkspaceEmail)} onSave={handleSave} isSaveDisabled={!canSave} /> @@ -125,8 +125,8 @@ export const SettingsNewEmailingDomain = () => { href: getSettingsPath(SettingsPath.Workspace), }, { - children: Emailing Domains, - href: getSettingsPath(SettingsPath.Workspace), + children: Email, + href: getSettingsPath(SettingsPath.WorkspaceEmail), }, { children: New Emailing Domain }, ]} diff --git a/packages/twenty-front/src/pages/settings/workspace/SettingsWorkspaceEmailGroupChannelDetail.tsx b/packages/twenty-front/src/pages/settings/workspace/SettingsWorkspaceEmailGroupChannelDetail.tsx index 16615dbb4c..2e261fa94e 100644 --- a/packages/twenty-front/src/pages/settings/workspace/SettingsWorkspaceEmailGroupChannelDetail.tsx +++ b/packages/twenty-front/src/pages/settings/workspace/SettingsWorkspaceEmailGroupChannelDetail.tsx @@ -76,7 +76,7 @@ export const SettingsWorkspaceEmailGroupChannelDetail = () => { const handleDelete = async () => { try { await deleteEmailGroupChannel(channel.id); - navigateSettings(SettingsPath.Workspace); + navigateSettings(SettingsPath.WorkspaceEmail); } catch { enqueueErrorSnackBar({ message: t`Failed to delete email handle.`, @@ -93,8 +93,8 @@ export const SettingsWorkspaceEmailGroupChannelDetail = () => { href: getSettingsPath(SettingsPath.Workspace), }, { - children: t`General`, - href: getSettingsPath(SettingsPath.Workspace), + children: t`Email`, + href: getSettingsPath(SettingsPath.WorkspaceEmail), }, { children: sourceHandle }, ]} diff --git a/packages/twenty-shared/src/types/SettingsPath.ts b/packages/twenty-shared/src/types/SettingsPath.ts index c124a9f622..cd0742d999 100644 --- a/packages/twenty-shared/src/types/SettingsPath.ts +++ b/packages/twenty-shared/src/types/SettingsPath.ts @@ -26,12 +26,13 @@ export enum SettingsPath { Workspace = 'general', Subdomain = 'general/subdomain', CustomDomain = 'general/custom-domain', - EmailGroupChannelDetail = 'general/email-group/:messageChannelId', - NewEmailGroupChannel = 'general/new-email-group', + WorkspaceEmail = 'email', + EmailGroupChannelDetail = 'email/email-group/:messageChannelId', + NewEmailGroupChannel = 'email/new-email-group', PublicDomain = 'applications/public-domain', NewApprovedAccessDomain = 'members/approved-access-domain/new', - NewEmailingDomain = 'general/emailing-domain/new', - EmailingDomainDetail = 'general/emailing-domain/:domainId', + NewEmailingDomain = 'email/emailing-domain/new', + EmailingDomainDetail = 'email/emailing-domain/:domainId', Updates = 'updates', AI = 'ai', AiUsageUserDetail = 'ai/usage/user/:userWorkspaceId',