diff --git a/packages/twenty-front/src/modules/activities/emails/constants/EmailRecipientLimits.ts b/packages/twenty-front/src/modules/activities/emails/constants/EmailRecipientLimits.ts index 172ed5a4204..b9f2b6d83a9 100644 --- a/packages/twenty-front/src/modules/activities/emails/constants/EmailRecipientLimits.ts +++ b/packages/twenty-front/src/modules/activities/emails/constants/EmailRecipientLimits.ts @@ -1,5 +1,2 @@ -// Maximum total recipients (To + Cc + Bcc) allowed per outbound email. -// Mirrors `MAX_EMAIL_RECIPIENTS` in twenty-server (email-tool.constants.ts). -// Surfaced on the frontend so the composer can disable Send and warn the user -// before the request is rejected by the backend. +// Mirrors MAX_EMAIL_RECIPIENTS in twenty-server (email-tool.constants.ts). export const MAX_EMAIL_RECIPIENTS = 100; diff --git a/packages/twenty-front/src/modules/activities/emails/hooks/useComposeEmailForTargetRecord.ts b/packages/twenty-front/src/modules/activities/emails/hooks/useComposeEmailForTargetRecord.ts index 87aa1ecddee..31870e388bd 100644 --- a/packages/twenty-front/src/modules/activities/emails/hooks/useComposeEmailForTargetRecord.ts +++ b/packages/twenty-front/src/modules/activities/emails/hooks/useComposeEmailForTargetRecord.ts @@ -6,9 +6,6 @@ import { SettingsPath } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; -// Wires the compose-email side panel to the record currently shown in the -// page (Person, Company or Opportunity). Used by both the inbox header `+` -// button and the empty-inbox CTA so they stay in sync. export const useComposeEmailForTargetRecord = () => { const targetRecord = useTargetRecord(); const { openComposeEmailInSidePanel } = useOpenComposeEmailInSidePanel(); diff --git a/packages/twenty-front/src/modules/activities/emails/hooks/useEmailComposerState.ts b/packages/twenty-front/src/modules/activities/emails/hooks/useEmailComposerState.ts index c154ddf65ca..28992a9a6b0 100644 --- a/packages/twenty-front/src/modules/activities/emails/hooks/useEmailComposerState.ts +++ b/packages/twenty-front/src/modules/activities/emails/hooks/useEmailComposerState.ts @@ -12,9 +12,6 @@ type UseEmailComposerStateArgs = { onSent?: () => void; }; -// Light-weight count of comma-separated recipients. Mirrors the backend -// `parseCommaSeparatedEmails` splitter so the disabled state matches what the -// server would actually accept — we just count, no validation. const countRecipients = (csv: string): number => csv .split(',') diff --git a/packages/twenty-front/src/modules/activities/emails/hooks/useOpenEmailInAppOrFallback.ts b/packages/twenty-front/src/modules/activities/emails/hooks/useOpenEmailInAppOrFallback.ts index c480374e600..db5057870cc 100644 --- a/packages/twenty-front/src/modules/activities/emails/hooks/useOpenEmailInAppOrFallback.ts +++ b/packages/twenty-front/src/modules/activities/emails/hooks/useOpenEmailInAppOrFallback.ts @@ -5,16 +5,11 @@ import { useOpenComposeEmailInSidePanel } from '@/side-panel/hooks/useOpenCompos import { isDefined } from 'twenty-shared/utils'; type UseOpenEmailInAppOrFallbackOptions = { - // When true the underlying connected-account query is skipped entirely, - // avoiding an unnecessary network request for non-email field types or when - // the click action is not OPEN_IN_APP. skip?: boolean; }; -// Opens the in-app email composer for the given email address. When the user -// has no connected account we cannot send through the side panel, so we fall -// back to the OS-level mailto handler instead of redirecting to settings — -// the user explicitly opted into "Open in app" and the link is still useful. +// Falls back to mailto: when no connected account exists, rather than +// redirecting to settings. export const useOpenEmailInAppOrFallback = ( options?: UseOpenEmailInAppOrFallbackOptions, ) => { diff --git a/packages/twenty-front/src/modules/activities/emails/hooks/useResolveDefaultEmailRecipient.ts b/packages/twenty-front/src/modules/activities/emails/hooks/useResolveDefaultEmailRecipient.ts index d203b2fc982..bd636c437cf 100644 --- a/packages/twenty-front/src/modules/activities/emails/hooks/useResolveDefaultEmailRecipient.ts +++ b/packages/twenty-front/src/modules/activities/emails/hooks/useResolveDefaultEmailRecipient.ts @@ -7,11 +7,6 @@ type UseResolveDefaultEmailRecipientParams = { recordId: string | null | undefined; }; -// Resolves the default `to` email recipient for a record so that the email -// composer can be opened pre-filled. The recipient depends on the object type: -// - person: the person's primary email -// - company: the primary email of the first person attached to the company -// - opportunity: the primary email of the opportunity's point of contact export const useResolveDefaultEmailRecipient = ({ objectNameSingular, recordId, diff --git a/packages/twenty-front/src/modules/activities/emails/hooks/useUploadEmailAttachment.ts b/packages/twenty-front/src/modules/activities/emails/hooks/useUploadEmailAttachment.ts index 228a01527e0..f0a7c49a770 100644 --- a/packages/twenty-front/src/modules/activities/emails/hooks/useUploadEmailAttachment.ts +++ b/packages/twenty-front/src/modules/activities/emails/hooks/useUploadEmailAttachment.ts @@ -12,10 +12,6 @@ import { formatFileSize } from '@/file/utils/formatFileSize'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { logError } from '~/utils/logError'; -// Email attachment uploads return the same `FileWithSignedUrlDTO` shape as -// every other typed upload mutation; we declare it locally to avoid depending -// on a codegen-generated document for this mutation (see comment in -// uploadEmailAttachmentFile.ts). type UploadEmailAttachmentFileResponse = { uploadEmailAttachmentFile: { id: string; @@ -38,10 +34,6 @@ export const useUploadEmailAttachment = () => { const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar(); const { t } = useLingui(); - // We reuse `WorkflowAttachment` as the in-memory shape because the SendEmail - // mutation input mirrors it field-for-field — sharing the type keeps the - // composer wiring trivial. The persisted file lives under the - // `email-attachment` folder, not the workflow folder. const uploadEmailAttachment = async ( file: File, ): Promise => { diff --git a/packages/twenty-front/src/modules/activities/emails/utils/getPrimaryEmailFromRecord.ts b/packages/twenty-front/src/modules/activities/emails/utils/getPrimaryEmailFromRecord.ts index 656641dcfad..d98da2d5e22 100644 --- a/packages/twenty-front/src/modules/activities/emails/utils/getPrimaryEmailFromRecord.ts +++ b/packages/twenty-front/src/modules/activities/emails/utils/getPrimaryEmailFromRecord.ts @@ -2,10 +2,6 @@ import { isNonEmptyString } from '@sniptt/guards'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; -// Reads `record.emails.primaryEmail` defensively. `ObjectRecord` is typed as -// `Record` so accessing nested fields is unchecked at compile -// time — we narrow at runtime so callers can rely on a `string | null` result -// without sprinkling `as` casts at every call site. export const getPrimaryEmailFromRecord = ( record: ObjectRecord, ): string | null => { diff --git a/packages/twenty-front/src/modules/command-menu-item/engine-command/global/components/ComposeEmailCommand.tsx b/packages/twenty-front/src/modules/command-menu-item/engine-command/global/components/ComposeEmailCommand.tsx index 1e5818f15ca..877e056994c 100644 --- a/packages/twenty-front/src/modules/command-menu-item/engine-command/global/components/ComposeEmailCommand.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/engine-command/global/components/ComposeEmailCommand.tsx @@ -26,17 +26,10 @@ export const ComposeEmailCommand = () => { const objectNameSingular = objectMetadataItem?.nameSingular ?? null; const isPerson = objectNameSingular === CoreObjectNameSingular.Person; - // Bulk means more than one record is selected *or* the user is in - // exclusion/"select all" mode where selectedRecords is empty but the - // graphqlFilter encodes the full target set. const isBulkPerson = isPerson && (selectedRecords.length > 1 || targetedRecordsRule.mode === 'exclusion'); - // For bulk Person selections we fetch emails directly from the server so - // we always have the data regardless of which columns are visible in the - // current view. Capped at MAX_EMAIL_RECIPIENTS since the backend will - // reject anything above that anyway. const { records: bulkPersonRecords, loading: bulkLoading } = useFindManyRecords({ objectNameSingular: CoreObjectNameSingular.Person, @@ -46,8 +39,6 @@ export const ComposeEmailCommand = () => { skip: !isBulkPerson, }); - // For single-record selection we use the shared resolver which handles - // the per-object-type fetching (Person, Company, Opportunity). const singleSelectedRecordId = !isBulkPerson ? (selectedRecords[0]?.id ?? null) : null; diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/EmailsFieldDisplay.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/EmailsFieldDisplay.tsx index 7ce13231913..359295017c6 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/EmailsFieldDisplay.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/meta-types/display/components/EmailsFieldDisplay.tsx @@ -11,9 +11,6 @@ export const EmailsFieldDisplay = () => { const { copyToClipboard } = useCopyToClipboard(); const { t } = useLingui(); - // Email fields default to opening the in-app composer when no setting is - // explicitly stored. The mailto-based stays as the rendered href so the - // link is still right-clickable / openable in a new tab. const onClickAction = fieldDefinition.metadata.settings?.clickAction ?? FieldMetadataSettingsOnClickAction.OPEN_IN_APP; @@ -21,8 +18,6 @@ export const EmailsFieldDisplay = () => { const isOpenInApp = onClickAction === FieldMetadataSettingsOnClickAction.OPEN_IN_APP; - // Only fire the connected-account query when the click action is - // OPEN_IN_APP — COPY and OPEN_LINK don't need it. const { openEmail } = useOpenEmailInAppOrFallback({ skip: !isOpenInApp }); const handleEmailClick = ( @@ -43,7 +38,6 @@ export const EmailsFieldDisplay = () => { return; } - // OPEN_LINK: let the native behaviour handle it. }; return ; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useGetSecondaryRecordTableCellButton.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useGetSecondaryRecordTableCellButton.ts index b8234881a4f..801d7b3f405 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useGetSecondaryRecordTableCellButton.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useGetSecondaryRecordTableCellButton.ts @@ -22,8 +22,6 @@ export const useGetSecondaryRecordTableCellButton = () => { const isEmailField = isFieldEmails(fieldDefinition); - // Only fire the connected-account query for email fields — phone and link - // fields never use the in-app composer so the request would be wasted. const { openEmail } = useOpenEmailInAppOrFallback({ skip: !isEmailField }); const fieldValue = useRecordFieldValue< @@ -39,8 +37,6 @@ export const useGetSecondaryRecordTableCellButton = () => { return []; } - // Email fields default to opening the in-app composer; other field types - // default to opening the platform link handler. const defaultClickAction = isEmailField ? FieldMetadataSettingsOnClickAction.OPEN_IN_APP : FieldMetadataSettingsOnClickAction.OPEN_LINK; @@ -48,9 +44,6 @@ export const useGetSecondaryRecordTableCellButton = () => { const mainActionOnClick = fieldDefinition.metadata.settings?.clickAction ?? defaultClickAction; - // The secondary button always exposes the "other" useful action: if main is - // a copy then offer an open action, otherwise offer copy. For emails the - // open action is the in-app composer rather than mailto. const openActionForFieldType = isEmailField ? FieldMetadataSettingsOnClickAction.OPEN_IN_APP : FieldMetadataSettingsOnClickAction.OPEN_LINK; diff --git a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldOnClickActionForm.tsx b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldOnClickActionForm.tsx index 8ae5cadebc4..c9a0654c72a 100644 --- a/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldOnClickActionForm.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/SettingsDataModelFieldOnClickActionForm.tsx @@ -37,8 +37,6 @@ export const SettingsDataModelFieldOnClickActionForm = ({ const isEmailField = fieldType === FieldMetadataType.EMAILS; - // For email fields we offer (and default to) opening the in-app composer. - // Other field types keep "Open as link" as the default. const defaultClickAction = isEmailField ? FieldMetadataSettingsOnClickAction.OPEN_IN_APP : FieldMetadataSettingsOnClickAction.OPEN_LINK; diff --git a/packages/twenty-server/src/engine/core-modules/file/file-email-attachment/services/file-email-attachment.service.ts b/packages/twenty-server/src/engine/core-modules/file/file-email-attachment/services/file-email-attachment.service.ts index 802ae576a11..edbd4e92da3 100644 --- a/packages/twenty-server/src/engine/core-modules/file/file-email-attachment/services/file-email-attachment.service.ts +++ b/packages/twenty-server/src/engine/core-modules/file/file-email-attachment/services/file-email-attachment.service.ts @@ -45,9 +45,6 @@ export class FileEmailAttachmentService { }, ); - // Email attachments are transient: once the email is sent, the local - // copy is no longer needed for retrieval. Mark as temporary so the - // file deletion job can sweep it. const savedFile = await this.fileStorageService.writeFile({ sourceFile: sanitizedFile, resourcePath: name, diff --git a/packages/twenty-server/src/engine/core-modules/tool/tools/email-tool/constants/email-tool.constants.ts b/packages/twenty-server/src/engine/core-modules/tool/tools/email-tool/constants/email-tool.constants.ts index 1e7ff60ec40..4234b909621 100644 --- a/packages/twenty-server/src/engine/core-modules/tool/tools/email-tool/constants/email-tool.constants.ts +++ b/packages/twenty-server/src/engine/core-modules/tool/tools/email-tool/constants/email-tool.constants.ts @@ -1,3 +1 @@ -// Maximum total recipients (To + Cc + Bcc) allowed per outbound email. -// Mirrored on the frontend in MAX_EMAIL_RECIPIENTS to provide early UX feedback. export const MAX_EMAIL_RECIPIENTS = 100; diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-command-menu-item.constant.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-command-menu-item.constant.ts index 075e3bf9b62..b0eaa4bf703 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-command-menu-item.constant.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-command-menu-item.constant.ts @@ -943,11 +943,6 @@ export const STANDARD_COMMAND_MENU_ITEMS = { hotKeys: null, payload: { path: '/settings/updates' }, }, - // Per-object Send Email items: same engine component as the global - // Compose Email command, but scoped to a record selection so the composer - // can be opened pre-filled with the selected record's email address. - // Person allows >= 1 so it works both from a record show page and from a - // bulk selection on the people index page. composeEmailToPerson: { universalIdentifier: 'f01d4b8b-2b4e-4ae0-9c6f-0b9a9a3e5b21', label: 'Send Email', diff --git a/packages/twenty-server/src/modules/messaging/message-outbound-manager/dtos/send-email.input.ts b/packages/twenty-server/src/modules/messaging/message-outbound-manager/dtos/send-email.input.ts index 8151482c028..0b07d6c80b7 100644 --- a/packages/twenty-server/src/modules/messaging/message-outbound-manager/dtos/send-email.input.ts +++ b/packages/twenty-server/src/modules/messaging/message-outbound-manager/dtos/send-email.input.ts @@ -1,10 +1,6 @@ import { Field, InputType } from '@nestjs/graphql'; -// Mirrors the `WorkflowAttachment` shape from twenty-shared (id/name/size/type/ -// createdAt). Inlined as a GraphQL input so the side-panel composer can pass -// already-uploaded files without depending on workflow types. If you change -// either side, keep both in sync — `EmailComposerService.getAttachments` -// resolves files by id, so the field names matter. +// Must stay in sync with WorkflowAttachment from twenty-shared. @InputType() export class SendEmailAttachmentInput { @Field(() => String)