diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts index a6f45b33ea..6439f697b4 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service.ts @@ -21,8 +21,8 @@ import { } from 'src/modules/messaging/message-import-manager/drivers/gmail/types/gmail-message.type'; import { MessagingMessageService } from 'src/modules/messaging/message-import-manager/services/messaging-message.service'; import { type MessageWithParticipants } from 'src/modules/messaging/message-import-manager/types/message'; +import { isGroupEmail } from 'src/modules/messaging/message-import-manager/utils/is-group-email'; import { MessagingMessageParticipantService } from 'src/modules/messaging/message-participant-manager/services/messaging-message-participant.service'; -import { isGroupEmail } from 'src/utils/is-group-email'; import { isWorkEmail } from 'src/utils/is-work-email'; @Injectable() diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/utils/__tests__/filter-emails.util.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/__tests__/filter-emails.util.spec.ts index eb80970c69..e11678741c 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/utils/__tests__/filter-emails.util.spec.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/__tests__/filter-emails.util.spec.ts @@ -1,3 +1,5 @@ +import { MessageDirection } from 'src/modules/messaging/common/enums/message-direction.enum'; +import { type MessageWithParticipants } from 'src/modules/messaging/message-import-manager/types/message'; import { messagingGetMessagesServiceGetMessages } from 'src/modules/messaging/message-import-manager/utils/__mocks__/messages'; import { filterEmails } from 'src/modules/messaging/message-import-manager/utils/filter-emails.util'; @@ -48,4 +50,141 @@ describe('filterEmails', () => { expect(filteredMessages).toEqual([]); }); + + it('should filter out messages from group email addresses', () => { + const primaryHandle = 'user@example.com'; + const messages: MessageWithParticipants[] = [ + { + externalId: 'noreply-message', + subject: 'Automated message', + receivedAt: new Date('2025-01-09T09:54:37.000Z'), + text: 'This is an automated message', + headerMessageId: '', + messageThreadExternalId: 'thread-1', + direction: MessageDirection.INCOMING, + participants: [ + { + role: 'from', + handle: 'noreply@example.com', + displayName: 'No Reply', + }, + ], + attachments: [], + }, + { + externalId: 'support-message', + subject: 'Support ticket', + receivedAt: new Date('2025-01-09T09:54:37.000Z'), + text: 'Support response', + headerMessageId: '', + messageThreadExternalId: 'thread-2', + direction: MessageDirection.INCOMING, + participants: [ + { + role: 'from', + handle: 'support@company.com', + displayName: 'Support Team', + }, + ], + attachments: [], + }, + { + externalId: 'regular-message', + subject: 'Personal message', + receivedAt: new Date('2025-01-09T10:54:37.000Z'), + text: 'This is a personal message', + headerMessageId: '', + messageThreadExternalId: 'thread-3', + direction: MessageDirection.INCOMING, + participants: [ + { + role: 'from', + handle: 'john@example.com', + displayName: 'John Doe', + }, + ], + attachments: [], + }, + ]; + + const result = filterEmails(primaryHandle, [], messages, []); + + expect(result).toHaveLength(1); + expect(result[0].externalId).toBe('regular-message'); + }); + + it('should not filter out group emails when excludeGroupEmails is false', () => { + const primaryHandle = 'user@example.com'; + const messages: MessageWithParticipants[] = [ + { + externalId: 'noreply-message', + subject: 'Automated message', + receivedAt: new Date('2025-01-09T09:54:37.000Z'), + text: 'This is an automated message', + headerMessageId: '', + messageThreadExternalId: 'thread-1', + direction: MessageDirection.INCOMING, + participants: [ + { + role: 'from', + handle: 'noreply@example.com', + displayName: 'No Reply', + }, + ], + attachments: [], + }, + ]; + + const result = filterEmails(primaryHandle, [], messages, [], false); + + expect(result).toEqual(messages); + }); + + it('should keep messages without participants', () => { + const primaryHandle = 'user@example.com'; + const messages: MessageWithParticipants[] = [ + { + externalId: 'no-participants', + subject: 'Test', + receivedAt: new Date('2025-01-09T09:54:37.000Z'), + text: 'Test content', + headerMessageId: '', + messageThreadExternalId: 'thread-1', + direction: MessageDirection.INCOMING, + participants: undefined as any, + attachments: [], + }, + ]; + + const result = filterEmails(primaryHandle, [], messages, []); + + expect(result).toEqual(messages); + }); + + it('should keep regular personal email addresses', () => { + const primaryHandle = 'user@example.com'; + const messages: MessageWithParticipants[] = [ + { + externalId: 'personal-1', + subject: 'Personal message', + receivedAt: new Date('2025-01-09T09:54:37.000Z'), + text: 'Content', + headerMessageId: '', + messageThreadExternalId: 'thread-1', + direction: MessageDirection.INCOMING, + participants: [ + { + role: 'from', + handle: 'john.doe@example.com', + displayName: 'John Doe', + }, + ], + attachments: [], + }, + ]; + + const result = filterEmails(primaryHandle, [], messages, []); + + expect(result).toEqual(messages); + }); }); diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-emails.util.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-emails.util.ts index f80c215400..f908c9ecec 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-emails.util.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-emails.util.ts @@ -1,14 +1,16 @@ -import { isEmailBlocklisted } from 'src/modules/blocklist/utils/is-email-blocklisted.util'; import { type MessageWithParticipants } from 'src/modules/messaging/message-import-manager/types/message'; -import { getDomainNameByEmail } from 'src/utils/get-domain-name-by-email'; +import { filterOutBlocklistedMessages } from 'src/modules/messaging/message-import-manager/utils/filter-out-blocklisted-messages.util'; +import { filterOutGroupEmails } from 'src/modules/messaging/message-import-manager/utils/filter-out-group-emails.util'; +import { filterOutIcsAttachments } from 'src/modules/messaging/message-import-manager/utils/filter-out-ics-attachments.util'; +import { filterOutInternals } from 'src/modules/messaging/message-import-manager/utils/filter-out-internals.util'; import { isWorkEmail } from 'src/utils/is-work-email'; -// Todo: refactor this into several utils export const filterEmails = ( primaryHandle: string, handleAliases: string[], messages: MessageWithParticipants[], blocklist: string[], + excludeGroupEmails: boolean = true, ) => { const messagesWithoutIcsAttachments = filterOutIcsAttachments(messages); @@ -22,68 +24,9 @@ export const filterEmails = ( ? filterOutInternals(primaryHandle, messagesWithoutBlocklisted) : messagesWithoutBlocklisted; - return messagesWithoutInternals; -}; - -const filterOutBlocklistedMessages = ( - messageChannelHandles: string[], - messages: MessageWithParticipants[], - blocklist: string[], -) => { - return messages.filter((message) => { - if (!message.participants) { - return true; - } - - return message.participants.every( - (participant) => - !isEmailBlocklisted( - messageChannelHandles, - participant.handle, - blocklist, - ), - ); - }); -}; - -const filterOutIcsAttachments = (messages: MessageWithParticipants[]) => { - return messages.filter((message) => { - if (!message.attachments) { - return true; - } - - return message.attachments.every( - (attachment) => !attachment.filename.endsWith('.ics'), - ); - }); -}; - -const filterOutInternals = ( - primaryHandle: string, - messages: MessageWithParticipants[], -) => { - return messages.filter((message) => { - if (!message.participants) { - return true; - } - - const primaryHandleDomain = getDomainNameByEmail(primaryHandle); - - try { - const isAllHandlesFromSameDomain = message.participants - .filter((participant) => !!participant.handle) - .every( - (participant) => - getDomainNameByEmail(participant.handle) === primaryHandleDomain, - ); - - if (isAllHandlesFromSameDomain) { - return false; - } - } catch { - return true; - } - - return true; - }); + const messagesWithoutInternalsAndGroupEmails = excludeGroupEmails + ? filterOutGroupEmails(messagesWithoutInternals) + : messagesWithoutInternals; + + return messagesWithoutInternalsAndGroupEmails; }; diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-out-blocklisted-messages.util.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-out-blocklisted-messages.util.ts new file mode 100644 index 0000000000..59aceb6173 --- /dev/null +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-out-blocklisted-messages.util.ts @@ -0,0 +1,23 @@ +import { isEmailBlocklisted } from 'src/modules/blocklist/utils/is-email-blocklisted.util'; +import { type MessageWithParticipants } from 'src/modules/messaging/message-import-manager/types/message'; + +export const filterOutBlocklistedMessages = ( + messageChannelHandles: string[], + messages: MessageWithParticipants[], + blocklist: string[], +) => { + return messages.filter((message) => { + if (!message.participants) { + return true; + } + + return message.participants.every( + (participant) => + !isEmailBlocklisted( + messageChannelHandles, + participant.handle, + blocklist, + ), + ); + }); +}; diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-out-group-emails.util.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-out-group-emails.util.ts new file mode 100644 index 0000000000..bd6b98c032 --- /dev/null +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-out-group-emails.util.ts @@ -0,0 +1,20 @@ +import { type MessageWithParticipants } from 'src/modules/messaging/message-import-manager/types/message'; +import { isGroupEmail } from 'src/modules/messaging/message-import-manager/utils/is-group-email'; + +export const filterOutGroupEmails = (messages: MessageWithParticipants[]) => { + return messages.filter((message) => { + if (!message.participants) { + return true; + } + + const fromParticipant = message.participants.find( + (participant) => participant.role === 'from', + ); + + if (!fromParticipant || !fromParticipant.handle) { + return true; + } + + return !isGroupEmail(fromParticipant.handle); + }); +}; diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-out-ics-attachments.util.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-out-ics-attachments.util.ts new file mode 100644 index 0000000000..8ca0482398 --- /dev/null +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-out-ics-attachments.util.ts @@ -0,0 +1,15 @@ +import { type MessageWithParticipants } from 'src/modules/messaging/message-import-manager/types/message'; + +export const filterOutIcsAttachments = ( + messages: MessageWithParticipants[], +) => { + return messages.filter((message) => { + if (!message.attachments) { + return true; + } + + return message.attachments.every( + (attachment) => !attachment.filename.endsWith('.ics'), + ); + }); +}; diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-out-internals.util.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-out-internals.util.ts new file mode 100644 index 0000000000..7b6a28576e --- /dev/null +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-out-internals.util.ts @@ -0,0 +1,32 @@ +import { type MessageWithParticipants } from 'src/modules/messaging/message-import-manager/types/message'; +import { getDomainNameByEmail } from 'src/utils/get-domain-name-by-email'; + +export const filterOutInternals = ( + primaryHandle: string, + messages: MessageWithParticipants[], +) => { + return messages.filter((message) => { + if (!message.participants) { + return true; + } + + const primaryHandleDomain = getDomainNameByEmail(primaryHandle); + + try { + const isAllHandlesFromSameDomain = message.participants + .filter((participant) => !!participant.handle) + .every( + (participant) => + getDomainNameByEmail(participant.handle) === primaryHandleDomain, + ); + + if (isAllHandlesFromSameDomain) { + return false; + } + } catch { + return true; + } + + return true; + }); +}; diff --git a/packages/twenty-server/src/utils/is-group-email.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/is-group-email.ts similarity index 100% rename from packages/twenty-server/src/utils/is-group-email.ts rename to packages/twenty-server/src/modules/messaging/message-import-manager/utils/is-group-email.ts