diff --git a/packages/twenty-server/src/engine/workspace-event-emitter/workspace-event-emitter.ts b/packages/twenty-server/src/engine/workspace-event-emitter/workspace-event-emitter.ts index a50b90edb4b..1178248be0d 100644 --- a/packages/twenty-server/src/engine/workspace-event-emitter/workspace-event-emitter.ts +++ b/packages/twenty-server/src/engine/workspace-event-emitter/workspace-event-emitter.ts @@ -15,8 +15,8 @@ import { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/t import { objectRecordChangedValues } from 'src/engine/core-modules/event-emitter/utils/object-record-changed-values'; import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; import { type CustomEventName } from 'src/engine/workspace-event-emitter/types/custom-event-name.type'; -import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; import { computeEventName } from 'src/engine/workspace-event-emitter/utils/compute-event-name'; +import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; type ActionEventMap = { [DatabaseEventAction.CREATED]: ObjectRecordCreateEvent; diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/calendar-event-import-manager.module.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/calendar-event-import-manager.module.ts index 254f187e450..bac02ae2015 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/calendar-event-import-manager.module.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/calendar-event-import-manager.module.ts @@ -10,6 +10,7 @@ import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repos import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; import { BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity'; import { CalendarEventCleanerModule } from 'src/modules/calendar/calendar-event-cleaner/calendar-event-cleaner.module'; +import { CalendarRelaunchFailedCalendarChannelsCommand } from 'src/modules/calendar/calendar-event-import-manager/commands/calendar-relaunch-failed-calendar-channels.command'; import { CalendarEventListFetchCronCommand } from 'src/modules/calendar/calendar-event-import-manager/crons/commands/calendar-event-list-fetch.cron.command'; import { CalendarEventsImportCronCommand } from 'src/modules/calendar/calendar-event-import-manager/crons/commands/calendar-import.cron.command'; import { CalendarOngoingStaleCronCommand } from 'src/modules/calendar/calendar-event-import-manager/crons/commands/calendar-ongoing-stale.cron.command'; @@ -64,6 +65,7 @@ import { RefreshTokensManagerModule } from 'src/modules/connected-account/refres CalendarEventsImportJob, CalendarOngoingStaleCronJob, CalendarOngoingStaleCronCommand, + CalendarRelaunchFailedCalendarChannelsCommand, CalendarOngoingStaleJob, ], exports: [ diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/commands/calendar-relaunch-failed-calendar-channels.command.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/commands/calendar-relaunch-failed-calendar-channels.command.ts new file mode 100644 index 00000000000..93957e12dd8 --- /dev/null +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/commands/calendar-relaunch-failed-calendar-channels.command.ts @@ -0,0 +1,85 @@ +import { InjectRepository } from '@nestjs/typeorm'; + +import { Command } from 'nest-commander'; +import { Repository } from 'typeorm'; + +import { + ActiveOrSuspendedWorkspacesMigrationCommandRunner, + type RunOnWorkspaceArgs, +} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { + CalendarChannelSyncStage, + CalendarChannelSyncStatus, + CalendarChannelWorkspaceEntity, +} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; +import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service'; + +@Command({ + name: 'calendar:relaunch-failed-calendar-channels', + description: 'Relaunch failed message channels', +}) +export class CalendarRelaunchFailedCalendarChannelsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { + constructor( + @InjectRepository(Workspace) + protected readonly workspaceRepository: Repository, + protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, + protected readonly accountsToReconnectService: AccountsToReconnectService, + ) { + super(workspaceRepository, twentyORMGlobalManager); + } + + override async runOnWorkspace({ + workspaceId, + options, + }: RunOnWorkspaceArgs): Promise { + try { + const calendarChannelRepository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, + 'calendarChannel', + { shouldBypassPermissionChecks: true }, + ); + + const failedCalendarChannels = await calendarChannelRepository.find({ + where: { + syncStage: CalendarChannelSyncStage.FAILED, + }, + relations: { + connectedAccount: { + accountOwner: true, + }, + }, + }); + + if (!options.dryRun && failedCalendarChannels.length > 0) { + await calendarChannelRepository.update( + failedCalendarChannels.map(({ id }) => id), + { + syncStage: + CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING, + syncStatus: CalendarChannelSyncStatus.ACTIVE, + }, + ); + + for (const failedCalendarChannel of failedCalendarChannels) { + await this.accountsToReconnectService.removeAccountToReconnect( + failedCalendarChannel.connectedAccount.accountOwner.userId, + failedCalendarChannel.connectedAccountId, + workspaceId, + ); + } + } + + this.logger.log( + `${options.dryRun ? ' (DRY RUN): ' : ''}Relaunched ${failedCalendarChannels.length} failed calendar channels`, + ); + } catch (error) { + this.logger.error( + 'Error while relaunching failed message channels', + error, + ); + } + } +} diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/jobs/calendar-event-list-fetch.cron.job.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/jobs/calendar-event-list-fetch.cron.job.ts index a9e8f2e8855..83af998a12e 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/jobs/calendar-event-list-fetch.cron.job.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/crons/jobs/calendar-event-list-fetch.cron.job.ts @@ -51,7 +51,7 @@ export class CalendarEventListFetchCronJob { const schemaName = getWorkspaceSchemaName(activeWorkspace.id); const calendarChannels = await this.coreDataSource.query( - `SELECT * FROM ${schemaName}."calendarChannel" WHERE "isSyncEnabled" = true AND "syncStage" IN ('${CalendarChannelSyncStage.FULL_CALENDAR_EVENT_LIST_FETCH_PENDING}', '${CalendarChannelSyncStage.PARTIAL_CALENDAR_EVENT_LIST_FETCH_PENDING}')`, + `SELECT * FROM ${schemaName}."calendarChannel" WHERE "isSyncEnabled" = true AND "syncStage" IN ('${CalendarChannelSyncStage.FULL_CALENDAR_EVENT_LIST_FETCH_PENDING}', '${CalendarChannelSyncStage.PARTIAL_CALENDAR_EVENT_LIST_FETCH_PENDING}', '${CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING}')`, ); for (const calendarChannel of calendarChannels) { diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/commands/messaging-relaunch-failed-message-channels.command.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/commands/messaging-relaunch-failed-message-channels.command.ts new file mode 100644 index 00000000000..630e71bfc5d --- /dev/null +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/commands/messaging-relaunch-failed-message-channels.command.ts @@ -0,0 +1,84 @@ +import { InjectRepository } from '@nestjs/typeorm'; + +import { Command } from 'nest-commander'; +import { Repository } from 'typeorm'; + +import { + ActiveOrSuspendedWorkspacesMigrationCommandRunner, + type RunOnWorkspaceArgs, +} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service'; +import { + MessageChannelSyncStage, + MessageChannelSyncStatus, + MessageChannelWorkspaceEntity, +} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; + +@Command({ + name: 'messaging:relaunch-failed-message-channels', + description: 'Relaunch failed message channels', +}) +export class MessagingRelaunchFailedMessageChannelsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { + constructor( + @InjectRepository(Workspace) + protected readonly workspaceRepository: Repository, + protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, + protected readonly accountsToReconnectService: AccountsToReconnectService, + ) { + super(workspaceRepository, twentyORMGlobalManager); + } + + override async runOnWorkspace({ + workspaceId, + options, + }: RunOnWorkspaceArgs): Promise { + try { + const messageChannelRepository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, + 'messageChannel', + { shouldBypassPermissionChecks: true }, + ); + + const failedMessageChannels = await messageChannelRepository.find({ + where: { + syncStage: MessageChannelSyncStage.FAILED, + }, + relations: { + connectedAccount: { + accountOwner: true, + }, + }, + }); + + if (!options.dryRun && failedMessageChannels.length > 0) { + await messageChannelRepository.update( + failedMessageChannels.map(({ id }) => id), + { + syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING, + syncStatus: MessageChannelSyncStatus.ACTIVE, + }, + ); + + for (const failedMessageChannel of failedMessageChannels) { + await this.accountsToReconnectService.removeAccountToReconnect( + failedMessageChannel.connectedAccount.accountOwner.userId, + failedMessageChannel.connectedAccountId, + workspaceId, + ); + } + } + + this.logger.log( + `${options.dryRun ? ' (DRY RUN): ' : ''}Relaunched ${failedMessageChannels.length} failed message channels`, + ); + } catch (error) { + this.logger.error( + 'Error while relaunching failed message channels', + error, + ); + } + } +} diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/crons/jobs/messaging-message-list-fetch.cron.job.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/crons/jobs/messaging-message-list-fetch.cron.job.ts index b3301d79e91..823c9d7d3db 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/crons/jobs/messaging-message-list-fetch.cron.job.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/crons/jobs/messaging-message-list-fetch.cron.job.ts @@ -50,7 +50,7 @@ export class MessagingMessageListFetchCronJob { // TODO: deprecate looking for FULL_MESSAGE_LIST_FETCH_PENDING as we introduce MESSAGE_LIST_FETCH_PENDING const messageChannels = await this.coreDataSource.query( - `SELECT * FROM ${schemaName}."messageChannel" WHERE "isSyncEnabled" = true AND "syncStage" IN ('${MessageChannelSyncStage.PARTIAL_MESSAGE_LIST_FETCH_PENDING}', '${MessageChannelSyncStage.FULL_MESSAGE_LIST_FETCH_PENDING}')`, + `SELECT * FROM ${schemaName}."messageChannel" WHERE "isSyncEnabled" = true AND "syncStage" IN ('${MessageChannelSyncStage.PARTIAL_MESSAGE_LIST_FETCH_PENDING}', '${MessageChannelSyncStage.FULL_MESSAGE_LIST_FETCH_PENDING}', '${MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING}')`, ); for (const messageChannel of messageChannels) { diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-fetch-by-batch.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-fetch-by-batch.service.ts index 63842833dec..41082a279a9 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-fetch-by-batch.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-fetch-by-batch.service.ts @@ -1,20 +1,13 @@ import { Injectable } from '@nestjs/common'; import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; -import { - MessageImportDriverException, - MessageImportDriverExceptionCode, -} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception'; import { MicrosoftClientProvider } from 'src/modules/messaging/message-import-manager/drivers/microsoft/providers/microsoft-client.provider'; import { type MicrosoftGraphBatchResponse } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.interface'; -import { MicrosoftHandleErrorService } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-handle-error.service'; -import { isAccessTokenRefreshingError } from 'src/modules/messaging/message-import-manager/drivers/microsoft/utils/is-access-token-refreshing-error.utils'; @Injectable() export class MicrosoftFetchByBatchService { constructor( private readonly microsoftClientProvider: MicrosoftClientProvider, - private readonly microsoftHandleErrorService: MicrosoftHandleErrorService, ) {} async fetchAllByBatches( @@ -49,23 +42,11 @@ export class MicrosoftFetchByBatchService { }, })); - try { - const batchResponse = await client - .api('/$batch') - .post({ requests: batchRequests }); + const batchResponse = await client + .api('/$batch') + .post({ requests: batchRequests }); - batchResponses.push(batchResponse); - } catch (error) { - if (isAccessTokenRefreshingError(error?.body)) { - throw new MessageImportDriverException( - error.message, - MessageImportDriverExceptionCode.CLIENT_NOT_AVAILABLE, - ); - } - this.microsoftHandleErrorService.handleMicrosoftMessageFetchByBatchError( - error, - ); - } + batchResponses.push(batchResponse); } return { @@ -73,22 +54,4 @@ export class MicrosoftFetchByBatchService { batchResponses, }; } - - /** - * Microsoft client.api.post sometimes throws (hard to catch) temporary errors like this one: - * - * { - * statusCode: 200, - * code: "SyntaxError", - * requestId: null, - * date: "2025-05-14T11:43:02.024Z", - * body: "SyntaxError: Unexpected token < in JSON at position 19341", - * headers: { - * }, - * } - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private _isTemporaryError(error: any): boolean { - return error?.body?.includes('Unexpected token < in JSON at position'); - } } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-handle-error.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-handle-error.service.ts index f45eb50cc48..68bbf813ed6 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-handle-error.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-handle-error.service.ts @@ -4,6 +4,7 @@ import { MessageImportDriverException, MessageImportDriverExceptionCode, } from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception'; +import { isAccessTokenRefreshingError } from 'src/modules/messaging/message-import-manager/drivers/microsoft/utils/is-access-token-refreshing-error.utils'; import { isMicrosoftClientTemporaryError } from 'src/modules/messaging/message-import-manager/drivers/microsoft/utils/is-temporary-error.utils'; import { parseMicrosoftMessagesImportError } from 'src/modules/messaging/message-import-manager/drivers/microsoft/utils/parse-microsoft-messages-import.util'; @@ -12,10 +13,20 @@ export class MicrosoftHandleErrorService { private readonly logger = new Logger(MicrosoftHandleErrorService.name); // eslint-disable-next-line @typescript-eslint/no-explicit-any - public handleMicrosoftMessageFetchByBatchError(error: any): void { - // TODO: remove this log once we catch better the error codes - this.logger.error(`Error temporary (${error.code}) fetching messages`); - this.logger.log(error); + public handleMicrosoftGetMessageListError(error: any): void { + this.logger.log(`Error fetching message list`, error); + throw parseMicrosoftMessagesImportError(error); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public handleMicrosoftGetMessagesError(error: any): void { + this.logger.log(`Error fetching messages`, error); + if (isAccessTokenRefreshingError(error?.body)) { + throw new MessageImportDriverException( + error.message, + MessageImportDriverExceptionCode.CLIENT_NOT_AVAILABLE, + ); + } const isBodyString = error.body && typeof error.body === 'string'; const isTemporaryError = @@ -28,71 +39,6 @@ export class MicrosoftHandleErrorService { ); } - if (!error.statusCode) { - throw new MessageImportDriverException( - `Microsoft Graph API unknown error: ${error}`, - MessageImportDriverExceptionCode.UNKNOWN, - ); - } - - const exception = parseMicrosoftMessagesImportError(error); - - if (exception) { - throw exception; - } - - throw new MessageImportDriverException( - `Microsoft driver error: ${error.message}`, - MessageImportDriverExceptionCode.UNKNOWN, - ); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - public handleMicrosoftGetMessageListError(error: any): void { - if (!error.statusCode) { - throw new MessageImportDriverException( - `Microsoft Graph API unknown error: ${error}`, - MessageImportDriverExceptionCode.UNKNOWN, - ); - } - - const exception = parseMicrosoftMessagesImportError(error); - - if (exception) { - throw exception; - } - - throw new MessageImportDriverException( - `Microsoft driver error: ${error.message}`, - MessageImportDriverExceptionCode.UNKNOWN, - ); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - public handleMicrosoftGetMessagesError(error: any): void { - if ( - error instanceof MessageImportDriverException && - error.code === MessageImportDriverExceptionCode.CLIENT_NOT_AVAILABLE - ) { - throw error; - } - - if (!error.statusCode) { - throw new MessageImportDriverException( - `Microsoft Graph API unknown error: ${error}`, - MessageImportDriverExceptionCode.UNKNOWN, - ); - } - - const exception = parseMicrosoftMessagesImportError(error); - - if (exception) { - throw exception; - } - - throw new MessageImportDriverException( - `Microsoft driver error: ${error.message}`, - MessageImportDriverExceptionCode.UNKNOWN, - ); + throw parseMicrosoftMessagesImportError(error); } } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/utils/parse-microsoft-messages-import.util.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/utils/parse-microsoft-messages-import.util.ts index 2a3bbd07207..a27b377be56 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/utils/parse-microsoft-messages-import.util.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/utils/parse-microsoft-messages-import.util.ts @@ -7,7 +7,7 @@ export const parseMicrosoftMessagesImportError = (error: { statusCode: number; message?: string; code?: string; -}): MessageImportDriverException | undefined => { +}): MessageImportDriverException => { if (error.statusCode === 401) { return new MessageImportDriverException( 'Unauthorized access to Microsoft Graph API', @@ -51,5 +51,8 @@ export const parseMicrosoftMessagesImportError = (error: { ); } - return undefined; + return new MessageImportDriverException( + `Microsoft Graph API unknown error: ${error} with status code ${error.statusCode}`, + MessageImportDriverExceptionCode.UNKNOWN, + ); }; diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/messaging-import-manager.module.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/messaging-import-manager.module.ts index b216a1e1b6d..3f1b0a83149 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/messaging-import-manager.module.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/messaging-import-manager.module.ts @@ -7,10 +7,13 @@ import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-s import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; import { WorkspaceEventEmitterModule } from 'src/engine/workspace-event-emitter/workspace-event-emitter.module'; +import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module'; import { EmailAliasManagerModule } from 'src/modules/connected-account/email-alias-manager/email-alias-manager.module'; import { RefreshTokensManagerModule } from 'src/modules/connected-account/refresh-tokens-manager/connected-account-refresh-tokens-manager.module'; import { MessagingCommonModule } from 'src/modules/messaging/common/messaging-common.module'; import { MessagingMessageCleanerModule } from 'src/modules/messaging/message-cleaner/messaging-message-cleaner.module'; +import { MessagingFolderSyncManagerModule } from 'src/modules/messaging/message-folder-manager/messaging-folder-sync-manager.module'; +import { MessagingRelaunchFailedMessageChannelsCommand } from 'src/modules/messaging/message-import-manager/commands/messaging-relaunch-failed-message-channels.command'; import { MessagingSingleMessageImportCommand } from 'src/modules/messaging/message-import-manager/commands/messaging-single-message-import.command'; import { MessagingMessageListFetchCronCommand } from 'src/modules/messaging/message-import-manager/crons/commands/messaging-message-list-fetch.cron.command'; import { MessagingMessagesImportCronCommand } from 'src/modules/messaging/message-import-manager/crons/commands/messaging-messages-import.cron.command'; @@ -39,7 +42,6 @@ import { MessagingMessagesImportService } from 'src/modules/messaging/message-im import { MessagingSaveMessagesAndEnqueueContactCreationService } from 'src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service'; import { MessagingSendMessageService } from 'src/modules/messaging/message-import-manager/services/messaging-send-message.service'; import { MessageParticipantManagerModule } from 'src/modules/messaging/message-participant-manager/message-participant-manager.module'; -import { MessagingFolderSyncManagerModule } from 'src/modules/messaging/message-folder-manager/messaging-folder-sync-manager.module'; import { MessagingMonitoringModule } from 'src/modules/messaging/monitoring/messaging-monitoring.module'; @Module({ imports: [ @@ -62,12 +64,14 @@ import { MessagingMonitoringModule } from 'src/modules/messaging/monitoring/mess MessagingMonitoringModule, MessagingMessageCleanerModule, WorkspaceEventEmitterModule, + ConnectedAccountModule, ], providers: [ MessagingMessageListFetchCronCommand, MessagingMessagesImportCronCommand, MessagingOngoingStaleCronCommand, MessagingSingleMessageImportCommand, + MessagingRelaunchFailedMessageChannelsCommand, MessagingMessageListFetchJob, MessagingMessagesImportJob, MessagingOngoingStaleJob,