diff --git a/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts b/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts index b75c3d6514cd..2e5365212518 100644 --- a/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts +++ b/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts @@ -50,6 +50,11 @@ export const seedFeatureFlags = async ( workspaceId: workspaceId, value: true, }, + { + key: FeatureFlagKeys.IsContactCreationForSentAndReceivedEmailsEnabled, + workspaceId: workspaceId, + value: true, + }, ]) .execute(); }; diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.entity.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.entity.ts index b4df67d205bb..03618d83bb52 100644 --- a/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.entity.ts @@ -23,6 +23,7 @@ export enum FeatureFlagKeys { IsStripeIntegrationEnabled = 'IS_STRIPE_INTEGRATION_ENABLED', IsGmailSyncV2Enabled = 'IS_GMAIL_SYNC_V2_ENABLED', IsLinksFieldEnabled = 'IS_LINKS_FIELD_ENABLED', + IsContactCreationForSentAndReceivedEmailsEnabled = 'IS_CONTACT_CREATION_FOR_SENT_AND_RECEIVED_EMAILS_ENABLED', } @Entity({ name: 'featureFlag', schema: 'core' }) diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/add-standard-id.command.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/add-standard-id.command.ts index 8a4043c14291..89e3136a9463 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/add-standard-id.command.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/add-standard-id.command.ts @@ -60,6 +60,7 @@ export class AddStandardIdCommand extends CommandRunner { IS_STRIPE_INTEGRATION_ENABLED: false, IS_GMAIL_SYNC_V2_ENABLED: true, IS_LINKS_FIELD_ENABLED: true, + IS_CONTACT_CREATION_FOR_SENT_AND_RECEIVED_EMAILS_ENABLED: true, }, ); const standardFieldMetadataCollection = this.standardFieldFactory.create( @@ -76,6 +77,7 @@ export class AddStandardIdCommand extends CommandRunner { IS_STRIPE_INTEGRATION_ENABLED: false, IS_GMAIL_SYNC_V2_ENABLED: true, IS_LINKS_FIELD_ENABLED: true, + IS_CONTACT_CREATION_FOR_SENT_AND_RECEIVED_EMAILS_ENABLED: true, }, ); diff --git a/packages/twenty-server/src/modules/messaging/jobs/messaging-create-company-and-contact-after-sync.job.ts b/packages/twenty-server/src/modules/messaging/jobs/messaging-create-company-and-contact-after-sync.job.ts index 27e3b5728f05..a51ad8c0f59b 100644 --- a/packages/twenty-server/src/modules/messaging/jobs/messaging-create-company-and-contact-after-sync.job.ts +++ b/packages/twenty-server/src/modules/messaging/jobs/messaging-create-company-and-contact-after-sync.job.ts @@ -1,7 +1,14 @@ import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; + +import { Repository } from 'typeorm'; import { MessageQueueJob } from 'src/engine/integrations/message-queue/interfaces/message-queue-job.interface'; +import { + FeatureFlagEntity, + FeatureFlagKeys, +} from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; import { CreateCompanyAndContactService } from 'src/modules/connected-account/auto-companies-and-contacts-creation/services/create-company-and-contact.service'; import { MessageChannelRepository } from 'src/modules/messaging/repositories/message-channel.repository'; @@ -27,6 +34,8 @@ export class MessagingCreateCompanyAndContactAfterSyncJob private readonly messageChannelService: MessageChannelRepository, @InjectObjectMetadataRepository(MessageParticipantWorkspaceEntity) private readonly messageParticipantRepository: MessageParticipantRepository, + @InjectRepository(FeatureFlagEntity, 'core') + private readonly featureFlagRepository: Repository, ) {} async handle( @@ -48,11 +57,25 @@ export class MessagingCreateCompanyAndContactAfterSyncJob return; } - const contactsToCreate = - await this.messageParticipantRepository.getByMessageChannelIdWithoutPersonIdAndWorkspaceMemberIdAndMessageOutgoing( - messageChannelId, - workspaceId, - ); + const isContactCreationForSentAndReceivedEmailsEnabledFeatureFlag = + await this.featureFlagRepository.findOneBy({ + workspaceId: workspaceId, + key: FeatureFlagKeys.IsContactCreationForSentAndReceivedEmailsEnabled, + value: true, + }); + + const isContactCreationForSentAndReceivedEmailsEnabled = + isContactCreationForSentAndReceivedEmailsEnabledFeatureFlag?.value; + + const contactsToCreate = isContactCreationForSentAndReceivedEmailsEnabled + ? await this.messageParticipantRepository.getByMessageChannelIdWithoutPersonIdAndWorkspaceMemberId( + messageChannelId, + workspaceId, + ) + : await this.messageParticipantRepository.getByMessageChannelIdWithoutPersonIdAndWorkspaceMemberIdAndMessageOutgoing( + messageChannelId, + workspaceId, + ); await this.createCompanyAndContactService.createCompaniesAndContactsAndUpdateParticipants( handle, diff --git a/packages/twenty-server/src/modules/messaging/repositories/message-participant.repository.ts b/packages/twenty-server/src/modules/messaging/repositories/message-participant.repository.ts index 63a3e90f5c98..4ae92c68abfa 100644 --- a/packages/twenty-server/src/modules/messaging/repositories/message-participant.repository.ts +++ b/packages/twenty-server/src/modules/messaging/repositories/message-participant.repository.ts @@ -131,6 +131,41 @@ export class MessageParticipantRepository { return messageParticipants; } + public async getByMessageChannelIdWithoutPersonIdAndWorkspaceMemberId( + messageChannelId: string, + workspaceId: string, + transactionManager?: EntityManager, + ): Promise { + if (!messageChannelId || !workspaceId) { + return []; + } + + const dataSourceSchema = + this.workspaceDataSourceService.getSchemaName(workspaceId); + + const messageParticipants: ParticipantWithId[] = + await this.workspaceDataSourceService.executeRawQuery( + `SELECT "messageParticipant".id, + "messageParticipant"."role", + "messageParticipant"."handle", + "messageParticipant"."displayName", + "messageParticipant"."personId", + "messageParticipant"."workspaceMemberId", + "messageParticipant"."messageId" + FROM ${dataSourceSchema}."messageParticipant" "messageParticipant" + LEFT JOIN ${dataSourceSchema}."message" ON "messageParticipant"."messageId" = ${dataSourceSchema}."message"."id" + LEFT JOIN ${dataSourceSchema}."messageChannelMessageAssociation" ON ${dataSourceSchema}."messageChannelMessageAssociation"."messageId" = ${dataSourceSchema}."message"."id" + WHERE ${dataSourceSchema}."messageChannelMessageAssociation"."messageChannelId" = $1 + AND "messageParticipant"."personId" IS NULL + AND "messageParticipant"."workspaceMemberId" IS NULL`, + [messageChannelId], + workspaceId, + transactionManager, + ); + + return messageParticipants; + } + public async getWithoutPersonIdAndWorkspaceMemberId( workspaceId: string, transactionManager?: EntityManager, diff --git a/packages/twenty-server/src/modules/messaging/services/gmail-messages-import/gmail-messages-import.module.ts b/packages/twenty-server/src/modules/messaging/services/gmail-messages-import/gmail-messages-import.module.ts index 6ceabf3e24c8..12b73f1e70fe 100644 --- a/packages/twenty-server/src/modules/messaging/services/gmail-messages-import/gmail-messages-import.module.ts +++ b/packages/twenty-server/src/modules/messaging/services/gmail-messages-import/gmail-messages-import.module.ts @@ -1,5 +1,7 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module'; import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; @@ -24,6 +26,7 @@ import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/standard-ob MessageModule, MessageParticipantModule, SetMessageChannelSyncStatusModule, + TypeOrmModule.forFeature([FeatureFlagEntity], 'core'), ], providers: [ GmailMessagesImportService, diff --git a/packages/twenty-server/src/modules/messaging/services/gmail-messages-import/gmail-messages-import.service.ts b/packages/twenty-server/src/modules/messaging/services/gmail-messages-import/gmail-messages-import.service.ts index c59721c0f4d5..788d65391375 100644 --- a/packages/twenty-server/src/modules/messaging/services/gmail-messages-import/gmail-messages-import.service.ts +++ b/packages/twenty-server/src/modules/messaging/services/gmail-messages-import/gmail-messages-import.service.ts @@ -1,6 +1,7 @@ import { Inject, Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; -import { EntityManager } from 'typeorm'; +import { EntityManager, Repository } from 'typeorm'; import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository'; @@ -31,6 +32,10 @@ import { CreateCompanyAndContactJobData, CreateCompanyAndContactJob, } from 'src/modules/connected-account/auto-companies-and-contacts-creation/jobs/create-company-and-contact.job'; +import { + FeatureFlagEntity, + FeatureFlagKeys, +} from 'src/engine/core-modules/feature-flag/feature-flag.entity'; @Injectable() export class GmailMessagesImportService { @@ -49,6 +54,8 @@ export class GmailMessagesImportService { private readonly messageQueueService: MessageQueueService, private readonly messageService: MessageService, private readonly messageParticipantService: MessageParticipantService, + @InjectRepository(FeatureFlagEntity, 'core') + private readonly featureFlagRepository: Repository, ) {} async fetchMessageContentFromCache( @@ -171,6 +178,16 @@ export class GmailMessagesImportService { const messageQueries = createQueriesFromMessageIds(messageIdsToFetch); + const isContactCreationForSentAndReceivedEmailsEnabledFeatureFlag = + await this.featureFlagRepository.findOneBy({ + workspaceId: workspaceId, + key: FeatureFlagKeys.IsContactCreationForSentAndReceivedEmailsEnabled, + value: true, + }); + + const isContactCreationForSentAndReceivedEmailsEnabled = + isContactCreationForSentAndReceivedEmailsEnabledFeatureFlag?.value; + try { const messagesToSave = await this.fetchMessagesByBatchesService.fetchAllMessages( @@ -214,8 +231,9 @@ export class GmailMessagesImportService { messageId, shouldCreateContact: gmailMessageChannel.isContactAutoCreationEnabled && - message.participants.find((p) => p.role === 'from') - ?.handle === connectedAccount.handle, + (isContactCreationForSentAndReceivedEmailsEnabled || + message.participants.find((p) => p.role === 'from') + ?.handle === connectedAccount.handle), })) : []; }); diff --git a/packages/twenty-server/src/modules/messaging/services/gmail-messages-import/save-messages-and-enqueue-contact-creation.service.ts b/packages/twenty-server/src/modules/messaging/services/gmail-messages-import/save-messages-and-enqueue-contact-creation.service.ts index fc99e3de2a18..63c07ba1a3c2 100644 --- a/packages/twenty-server/src/modules/messaging/services/gmail-messages-import/save-messages-and-enqueue-contact-creation.service.ts +++ b/packages/twenty-server/src/modules/messaging/services/gmail-messages-import/save-messages-and-enqueue-contact-creation.service.ts @@ -1,6 +1,7 @@ import { Injectable, Inject } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; -import { EntityManager } from 'typeorm'; +import { EntityManager, Repository } from 'typeorm'; import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/integrations/message-queue/services/message-queue.service'; @@ -18,6 +19,10 @@ import { CreateCompanyAndContactJobData, CreateCompanyAndContactJob, } from 'src/modules/connected-account/auto-companies-and-contacts-creation/jobs/create-company-and-contact.job'; +import { + FeatureFlagEntity, + FeatureFlagKeys, +} from 'src/engine/core-modules/feature-flag/feature-flag.entity'; @Injectable() export class SaveMessagesAndEnqueueContactCreationService { @@ -27,6 +32,8 @@ export class SaveMessagesAndEnqueueContactCreationService { private readonly messageQueueService: MessageQueueService, private readonly messageService: MessageService, private readonly messageParticipantService: MessageParticipantService, + @InjectRepository(FeatureFlagEntity, 'core') + private readonly featureFlagRepository: Repository, ) {} async saveMessagesAndEnqueueContactCreationJob( @@ -40,6 +47,16 @@ export class SaveMessagesAndEnqueueContactCreationService { workspaceId, ); + const isContactCreationForSentAndReceivedEmailsEnabledFeatureFlag = + await this.featureFlagRepository.findOneBy({ + workspaceId: workspaceId, + key: FeatureFlagKeys.IsContactCreationForSentAndReceivedEmailsEnabled, + value: true, + }); + + const isContactCreationForSentAndReceivedEmailsEnabled = + isContactCreationForSentAndReceivedEmailsEnabledFeatureFlag?.value; + const participantsWithMessageId = await workspaceDataSource?.transaction( async (transactionManager: EntityManager) => { const messageExternalIdsAndIdsMap = @@ -62,8 +79,9 @@ export class SaveMessagesAndEnqueueContactCreationService { messageId, shouldCreateContact: messageChannel.isContactAutoCreationEnabled && - message.participants.find((p) => p.role === 'from') - ?.handle === connectedAccount.handle, + (isContactCreationForSentAndReceivedEmailsEnabled || + message.participants.find((p) => p.role === 'from') + ?.handle === connectedAccount.handle), })) : []; });