feat(settings): move email handles and emailing domains to dedicated Email page (#21008)

## 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.
This commit is contained in:
Félix Malfait
2026-05-28 13:22:55 +02:00
committed by GitHub
parent 7065441972
commit 7d9f9605a2
9 changed files with 85 additions and 34 deletions
@@ -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) => (
}
>
<Route path={SettingsPath.Workspace} element={<SettingsWorkspace />} />
<Route
path={SettingsPath.WorkspaceEmail}
element={<SettingsWorkspaceEmail />}
/>
<Route
path={SettingsPath.NewEmailGroupChannel}
element={<SettingsNewEmailGroupChannel />}
@@ -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}
/>
}
@@ -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,
@@ -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 (
<SubMenuTopBarContainer
title={t`General`}
@@ -61,10 +49,6 @@ export const SettingsWorkspace = () => {
<SettingsWorkspaceDomainCard />
</Section>
)}
{showEmailGroupSection && <SettingsWorkspaceEmailGroupSection />}
{showEmailingDomainsSection && (
<SettingsWorkspaceEmailingDomainsSection />
)}
<Section>
<DeleteWorkspace />
</Section>
@@ -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 (
<SubMenuTopBarContainer
title={t`Email`}
links={[
{
children: t`Workspace`,
href: getSettingsPath(SettingsPath.Workspace),
},
{ children: t`Email` },
]}
>
<SettingsPageContainer>
{showEmailGroupSection && <SettingsWorkspaceEmailGroupSection />}
<SettingsWorkspaceEmailingDomainsSection />
</SettingsPageContainer>
</SubMenuTopBarContainer>
);
};
@@ -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: <Trans>Emailing Domains</Trans>,
href: getSettingsPath(SettingsPath.Workspace),
children: <Trans>Email</Trans>,
href: getSettingsPath(SettingsPath.WorkspaceEmail),
},
{ children: emailingDomain.domain },
]}
@@ -114,7 +114,7 @@ export const SettingsNewEmailingDomain = () => {
title={t`New Emailing Domain`}
actionButton={
<SaveAndCancelButtons
onCancel={() => navigate(SettingsPath.Workspace)}
onCancel={() => navigate(SettingsPath.WorkspaceEmail)}
onSave={handleSave}
isSaveDisabled={!canSave}
/>
@@ -125,8 +125,8 @@ export const SettingsNewEmailingDomain = () => {
href: getSettingsPath(SettingsPath.Workspace),
},
{
children: <Trans>Emailing Domains</Trans>,
href: getSettingsPath(SettingsPath.Workspace),
children: <Trans>Email</Trans>,
href: getSettingsPath(SettingsPath.WorkspaceEmail),
},
{ children: <Trans>New Emailing Domain</Trans> },
]}
@@ -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 },
]}
@@ -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',