diff --git a/packages/twenty-server/src/engine/core-modules/auth/auth.module.ts b/packages/twenty-server/src/engine/core-modules/auth/auth.module.ts index 88e1cfe248..1c1bcf16f6 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/auth.module.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/auth.module.ts @@ -145,6 +145,11 @@ import { JwtAuthStrategy } from './strategies/jwt.auth.strategy'; ApiKeyService, AuthSsoService, ], - exports: [AccessTokenService, LoginTokenService, RefreshTokenService], + exports: [ + AccessTokenService, + LoginTokenService, + RefreshTokenService, + CreateMessageFolderService, + ], }) export class AuthModule {} diff --git a/packages/twenty-server/src/modules/connected-account/imap-api/imap-apis.module.ts b/packages/twenty-server/src/modules/connected-account/imap-api/imap-apis.module.ts index 761c92c41c..bdea02f86f 100644 --- a/packages/twenty-server/src/modules/connected-account/imap-api/imap-apis.module.ts +++ b/packages/twenty-server/src/modules/connected-account/imap-api/imap-apis.module.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { AuthModule } from 'src/engine/core-modules/auth/auth.module'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { MessageQueueModule } from 'src/engine/core-modules/message-queue/message-queue.module'; import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module'; @@ -17,6 +18,7 @@ import { ImapSmtpCalDavAPIService } from 'src/modules/connected-account/services TwentyConfigModule, TwentyORMModule, FeatureFlagModule, + AuthModule, ], providers: [ImapSmtpCalDavAPIService], exports: [ImapSmtpCalDavAPIService], diff --git a/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.ts b/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.ts index 65d46b5c43..35dcb2854d 100644 --- a/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.ts +++ b/packages/twenty-server/src/modules/connected-account/services/imap-smtp-caldav-apis.service.ts @@ -3,10 +3,12 @@ import { Injectable } from '@nestjs/common'; import { ConnectedAccountProvider } from 'twenty-shared/types'; import { v4 } from 'uuid'; +import { CreateMessageFolderService } from 'src/engine/core-modules/auth/services/create-message-folder.service'; import { EmailAccountConnectionParameters } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.dto'; import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; +import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { @@ -38,6 +40,7 @@ export class ImapSmtpCalDavAPIService { private readonly messageQueueService: MessageQueueService, @InjectMessageQueue(MessageQueue.calendarQueue) private readonly calendarQueueService: MessageQueueService, + private readonly createMessageFolderService: CreateMessageFolderService, ) {} async setupCompleteAccount(input: { @@ -86,25 +89,28 @@ export class ImapSmtpCalDavAPIService { let createdMessageChannel: MessageChannelWorkspaceEntity | null = null; let createdCalendarChannel: CalendarChannelWorkspaceEntity | null = null; - await workspaceDataSource.transaction(async () => { - await this.upsertConnectedAccount( - input, - accountId, - connectedAccountRepository, - ); + await workspaceDataSource.transaction( + async (manager: WorkspaceEntityManager) => { + await this.upsertConnectedAccount( + input, + accountId, + connectedAccountRepository, + ); - createdMessageChannel = await this.setupMessageChannels( - input, - accountId, - messageChannelRepository, - ); + createdMessageChannel = await this.setupMessageChannels( + input, + accountId, + messageChannelRepository, + manager, + ); - createdCalendarChannel = await this.setupCalendarChannels( - input, - accountId, - calendarChannelRepository, - ); - }); + createdCalendarChannel = await this.setupCalendarChannels( + input, + accountId, + calendarChannelRepository, + ); + }, + ); await this.enqueueSyncJobs( input, @@ -143,6 +149,7 @@ export class ImapSmtpCalDavAPIService { }, accountId: string, messageChannelRepository: WorkspaceRepository, + manager: WorkspaceEntityManager, ): Promise { const existingChannels = await messageChannelRepository.find({ where: { connectedAccountId: accountId }, @@ -175,6 +182,12 @@ export class ImapSmtpCalDavAPIService { {}, ); + await this.createMessageFolderService.createMessageFolders({ + workspaceId: input.workspaceId, + messageChannelId: newMessageChannel.id, + manager, + }); + return shouldEnableSync ? newMessageChannel : null; } 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 491a45ae02..7144be4682 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 @@ -4,9 +4,13 @@ import { ImapFlow } from 'imapflow'; import { ImapClientProvider } from 'src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider'; import { ImapHandleErrorService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-handle-error.service'; +import { MessageFolderName } from 'src/modules/messaging/message-import-manager/drivers/imap/types/folders'; import { findSentMailbox } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/find-sent-mailbox.util'; import { GetMessageListsArgs } from 'src/modules/messaging/message-import-manager/types/get-message-lists-args.type'; -import { GetMessageListsResponse } from 'src/modules/messaging/message-import-manager/types/get-message-lists-response.type'; +import { + GetMessageListsResponse, + GetOneMessageListResponse, +} from 'src/modules/messaging/message-import-manager/types/get-message-lists-response.type'; @Injectable() export class ImapGetMessageListService { @@ -20,59 +24,38 @@ export class ImapGetMessageListService { async getMessageLists({ messageChannel, connectedAccount, + messageFolders, }: GetMessageListsArgs): Promise { try { const client = await this.imapClientProvider.getClient(connectedAccount); + const result: GetMessageListsResponse = []; - const mailboxes = ['INBOX']; + for (const folder of messageFolders) { + const mailboxName = await this.getMailboxName(client, folder.name); - const sentFolder = await findSentMailbox(client, this.logger); + if (!mailboxName) { + continue; + } - if (sentFolder) { - mailboxes.push(sentFolder); - } - - let allMessages: { id: string; date: string }[] = []; - - for (const mailbox of mailboxes) { try { - const messages = await this.getMessagesFromMailbox( + const response = await this.getMessageListForMailbox( client, - mailbox, - messageChannel.syncCursor, + mailboxName, + folder.syncCursor, ); - allMessages = [...allMessages, ...messages]; - this.logger.log( - `Fetched ${messages.length} messages from ${mailbox}`, - ); + result.push({ + ...response, + folderId: folder.id, + }); } catch (error) { this.logger.warn( - `Error fetching from mailbox ${mailbox}: ${error.message}. Continuing with other mailboxes.`, + `Error fetching from folder ${folder.name} (${mailboxName}): ${error.message}. Continuing with other folders.`, ); } } - allMessages.sort( - (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(), - ); - - const messageExternalIds = allMessages.map((message) => message.id); - - const nextSyncCursor = - allMessages.length > 0 - ? allMessages[allMessages.length - 1].date - : messageChannel.syncCursor || ''; - - return [ - { - messageExternalIds, - nextSyncCursor, - previousSyncCursor: messageChannel.syncCursor, - messageExternalIdsToDelete: [], - folderId: undefined, - }, - ]; + return result; } catch (error) { this.logger.error( `Error getting message list: ${error.message}`, @@ -95,6 +78,54 @@ export class ImapGetMessageListService { } } + private async getMailboxName( + client: ImapFlow, + folderName: string, + ): Promise { + if (folderName === MessageFolderName.INBOX) { + return 'INBOX'; + } + + if (folderName === MessageFolderName.SENT_ITEMS) { + const sentMailbox = await findSentMailbox(client, this.logger); + + if (!sentMailbox) { + this.logger.warn('SENT folder not found, skipping'); + + return null; + } + + return sentMailbox; + } + + return folderName; + } + + private async getMessageListForMailbox( + client: ImapFlow, + mailbox: string, + cursor?: string, + ): Promise { + const messages = await this.getMessagesFromMailbox(client, mailbox, cursor); + + messages.sort( + (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(), + ); + + const messageExternalIds = messages.map((message) => message.id); + + const nextSyncCursor = + messages.length > 0 ? messages[messages.length - 1].date : cursor || ''; + + return { + messageExternalIds, + nextSyncCursor, + previousSyncCursor: cursor || '', + messageExternalIdsToDelete: [], + folderId: undefined, + }; + } + private async getMessagesFromMailbox( client: ImapFlow, mailbox: string, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/types/folders.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/types/folders.ts new file mode 100644 index 0000000000..aeb45fc873 --- /dev/null +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/types/folders.ts @@ -0,0 +1,4 @@ +export enum MessageFolderName { + INBOX = 'inbox', + SENT_ITEMS = 'sentItems', +}