From a3f2fafce6af67e5f4d14c72386c05d572a41c66 Mon Sep 17 00:00:00 2001 From: neo773 <62795688+neo773@users.noreply.github.com> Date: Tue, 5 May 2026 17:58:03 +0530 Subject: [PATCH] fix smtp outbound persist message (#20276) `APPEND` used display name `Sent` instead of `INBOX.Sent` Fix is to use mailbox path, extreacted this as a utility, all services are consistent now. /closes #20267 --- .../services/imap-get-all-folders.service.ts | 13 +++------- .../services/imap-get-message-list.service.ts | 6 ++--- .../get-imap-folder-path.util.spec.ts | 25 +++++++++++++++++++ .../imap/utils/get-imap-folder-path.util.ts | 23 +++++++++++++++++ .../imap-smtp-message-outbound.service.ts | 7 ++++-- 5 files changed, 60 insertions(+), 14 deletions(-) create mode 100644 packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/utils/__tests__/get-imap-folder-path.util.spec.ts create mode 100644 packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/utils/get-imap-folder-path.util.ts diff --git a/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/imap/services/imap-get-all-folders.service.ts b/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/imap/services/imap-get-all-folders.service.ts index 975f7d34b5..658cb6da0c 100644 --- a/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/imap/services/imap-get-all-folders.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/imap/services/imap-get-all-folders.service.ts @@ -14,6 +14,7 @@ import { shouldCreateFolderByDefault } from 'src/modules/messaging/message-folde import { shouldSyncFolderByDefault } from 'src/modules/messaging/message-folder-manager/utils/should-sync-folder-by-default.util'; import { ImapClientProvider } from 'src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider'; import { ImapFindSentFolderService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-find-sent-folder.service'; +import { getImapFolderPath } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/get-imap-folder-path.util'; import { getStandardFolderByRegex } from 'src/modules/messaging/message-import-manager/drivers/utils/get-standard-folder-by-regex'; @Injectable() @@ -163,15 +164,9 @@ export class ImapGetAllFoldersService implements MessageFolderDriver { return false; } - const isDuplicate = existingFolders.some((folder) => { - const folderPath = folder?.externalId?.split(':')[0]; - - if (!isDefined(folderPath)) { - return false; - } - - return folderPath === mailbox.path; - }); + const isDuplicate = existingFolders.some( + (folder) => getImapFolderPath(folder?.externalId) === mailbox.path, + ); return !isDuplicate; } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-message-list.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-message-list.service.ts index 74faf29bb5..60da6d6b74 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-message-list.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-message-list.service.ts @@ -15,7 +15,7 @@ import { ImapMessageListFetchErrorHandler } from 'src/modules/messaging/message- import { ImapSyncService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-sync.service'; import { createSyncCursor } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/create-sync-cursor.util'; import { extractMailboxState } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/extract-mailbox-state.util'; -import { parseMessageId } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/parse-message-id.util'; +import { getImapFolderPath } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/get-imap-folder-path.util'; import { parseSyncCursor } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/parse-sync-cursor.util'; import { type GetMessageListsArgs } from 'src/modules/messaging/message-import-manager/types/get-message-lists-args.type'; import { @@ -79,7 +79,7 @@ export class ImapGetMessageListService { client: ImapFlow, folder: MessageFolder, ): Promise { - const folderPath = parseMessageId(folder.externalId ?? '')?.folder; + const folderPath = getImapFolderPath(folder.externalId); if (!isDefined(folderPath)) { throw new MessageImportDriverException( @@ -157,7 +157,7 @@ export class ImapGetMessageListService { client: ImapFlow, folder: MessageFolder, ): Promise { - const folderPath = parseMessageId(folder.externalId ?? '')?.folder; + const folderPath = getImapFolderPath(folder.externalId); const previousCursor = parseSyncCursor(folder.syncCursor); if (!isDefined(folderPath) || !isDefined(previousCursor)) { diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/utils/__tests__/get-imap-folder-path.util.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/utils/__tests__/get-imap-folder-path.util.spec.ts new file mode 100644 index 0000000000..ece8b18be7 --- /dev/null +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/utils/__tests__/get-imap-folder-path.util.spec.ts @@ -0,0 +1,25 @@ +import { getImapFolderPath } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/get-imap-folder-path.util'; + +describe('getImapFolderPath', () => { + it('extracts the path from a `path:uidValidity` externalId', () => { + expect(getImapFolderPath('INBOX.Sent:1768984533')).toBe('INBOX.Sent'); + }); + + it('returns the externalId unchanged when it has no uidValidity suffix', () => { + expect(getImapFolderPath('INBOX')).toBe('INBOX'); + }); + + it('preserves colons inside the path and only strips the trailing uidValidity', () => { + expect(getImapFolderPath('Foo:Bar:42')).toBe('Foo:Bar'); + }); + + it('returns the externalId unchanged when the trailing segment is non-numeric', () => { + expect(getImapFolderPath('Project: Updates')).toBe('Project: Updates'); + }); + + it('returns null for empty, null, or undefined input', () => { + expect(getImapFolderPath('')).toBeNull(); + expect(getImapFolderPath(null)).toBeNull(); + expect(getImapFolderPath(undefined)).toBeNull(); + }); +}); diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/utils/get-imap-folder-path.util.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/utils/get-imap-folder-path.util.ts new file mode 100644 index 0000000000..a780022c5a --- /dev/null +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/utils/get-imap-folder-path.util.ts @@ -0,0 +1,23 @@ +import { isNonEmptyString } from '@sniptt/guards'; + +export const getImapFolderPath = ( + externalId: string | null | undefined, +): string | null => { + if (!isNonEmptyString(externalId)) { + return null; + } + + const lastColonIndex = externalId.lastIndexOf(':'); + + if (lastColonIndex === -1) { + return externalId; + } + + const trailingSegment = externalId.slice(lastColonIndex + 1); + + if (!/^\d+$/.test(trailingSegment)) { + return externalId; + } + + return externalId.slice(0, lastColonIndex); +}; diff --git a/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/imap/services/imap-smtp-message-outbound.service.ts b/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/imap/services/imap-smtp-message-outbound.service.ts index 1434470bb1..ae9fa2832e 100644 --- a/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/imap/services/imap-smtp-message-outbound.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-outbound-manager/drivers/imap/services/imap-smtp-message-outbound.service.ts @@ -12,6 +12,7 @@ import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/ import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; import { ImapClientProvider } from 'src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider'; import { ImapFindDraftsFolderService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-find-drafts-folder.service'; +import { getImapFolderPath } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/get-imap-folder-path.util'; import { SmtpClientProvider } from 'src/modules/messaging/message-import-manager/drivers/smtp/providers/smtp-client.provider'; import { type SendMessageInput } from 'src/modules/messaging/message-outbound-manager/types/send-message-input.type'; import { type SendMessageResult } from 'src/modules/messaging/message-outbound-manager/types/send-message-result.type'; @@ -76,8 +77,10 @@ export class ImapSmtpMessageOutboundService implements MessageOutboundDriver { }); } - if (isDefined(sentFolder) && isDefined(sentFolder.name)) { - await imapClient.append(sentFolder.name, messageBuffer); + const sentFolderPath = getImapFolderPath(sentFolder?.externalId); + + if (isDefined(sentFolderPath)) { + await imapClient.append(sentFolderPath, messageBuffer); } await this.imapClientProvider.closeClient(imapClient);