diff --git a/packages/twenty-server/src/modules/connected-account/email-alias-manager/email-alias-manager.module.ts b/packages/twenty-server/src/modules/connected-account/email-alias-manager/email-alias-manager.module.ts index 03ddb6b2b2..dfdc3d2fa7 100644 --- a/packages/twenty-server/src/modules/connected-account/email-alias-manager/email-alias-manager.module.ts +++ b/packages/twenty-server/src/modules/connected-account/email-alias-manager/email-alias-manager.module.ts @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { GmailEmailAliasErrorHandlerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-error-handler.service'; import { GoogleEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-manager.service'; import { MicrosoftEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/microsoft/services/microsoft-email-alias-manager.service'; @@ -11,7 +12,7 @@ import { OAuth2ClientManagerModule } from 'src/modules/connected-account/oauth2- @Module({ imports: [ OAuth2ClientManagerModule, - TypeOrmModule.forFeature([ConnectedAccountEntity]), + TypeOrmModule.forFeature([ConnectedAccountEntity, MessageChannelEntity]), ], providers: [ EmailAliasManagerService, diff --git a/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.spec.ts b/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.spec.ts index 757893c28e..749d2e4a59 100644 --- a/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.spec.ts +++ b/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.spec.ts @@ -4,6 +4,7 @@ import { getRepositoryToken } from '@nestjs/typeorm'; import { ConnectedAccountProvider } from 'twenty-shared/types'; import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { GoogleEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-manager.service'; import { microsoftGraphMeResponseWithProxyAddresses } from 'src/modules/connected-account/email-alias-manager/drivers/microsoft/mocks/microsoft-api-examples'; @@ -19,6 +20,13 @@ describe('Email Alias Manager Service', () => { // @ts-expect-error legacy noImplicitAny update: jest.fn().mockResolvedValue((arg) => arg), }; + const mockMessageChannelRepository = { + exists: jest.fn().mockResolvedValue(true), + }; + + afterEach(() => { + jest.clearAllMocks(); + }); beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -36,6 +44,10 @@ describe('Email Alias Manager Service', () => { provide: getRepositoryToken(ConnectedAccountEntity), useValue: mockConnectedAccountRepository, }, + { + provide: getRepositoryToken(MessageChannelEntity), + useValue: mockMessageChannelRepository, + }, { provide: GoogleEmailAliasManagerService, useValue: {}, @@ -102,4 +114,30 @@ describe('Email Alias Manager Service', () => { ); }); }); + + describe('Refresh handle aliases without a mailbox', () => { + it('Should preserve existing aliases and skip the update when the account has no message channel', async () => { + mockMessageChannelRepository.exists.mockResolvedValueOnce(false); + + const existingAliases = ['existing@domain.com']; + const mockConnectedAccount: Partial = { + id: 'test-id', + provider: ConnectedAccountProvider.GOOGLE, + handleAliases: existingAliases, + }; + + jest.spyOn(microsoftEmailAliasManagerService, 'getHandleAliases'); + + const result = await emailAliasManagerService.refreshHandleAliases( + mockConnectedAccount as ConnectedAccountEntity, + 'test-workspace-id', + ); + + expect(result).toEqual(existingAliases); + expect( + microsoftEmailAliasManagerService.getHandleAliases, + ).not.toHaveBeenCalled(); + expect(mockConnectedAccountRepository.update).not.toHaveBeenCalled(); + }); + }); }); diff --git a/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.ts b/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.ts index 16bea3cca8..175b41f9aa 100644 --- a/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.ts +++ b/packages/twenty-server/src/modules/connected-account/email-alias-manager/services/email-alias-manager.service.ts @@ -6,6 +6,7 @@ import { assertUnreachable } from 'twenty-shared/utils'; import { Repository } from 'typeorm'; import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity'; +import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; import { GoogleEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-manager.service'; @@ -19,41 +20,25 @@ export class EmailAliasManagerService { private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, @InjectRepository(ConnectedAccountEntity) private readonly connectedAccountRepository: Repository, + @InjectRepository(MessageChannelEntity) + private readonly messageChannelRepository: Repository, ) {} public async refreshHandleAliases( connectedAccount: ConnectedAccountEntity, workspaceId: string, ): Promise { - let handleAliases: string[]; + const accountHasMailbox = await this.messageChannelRepository.exists({ + where: { connectedAccountId: connectedAccount.id, workspaceId }, + }); - switch (connectedAccount.provider) { - case ConnectedAccountProvider.MICROSOFT: - handleAliases = - await this.microsoftEmailAliasManagerService.getHandleAliases( - connectedAccount, - ); - break; - case ConnectedAccountProvider.GOOGLE: - handleAliases = - await this.googleEmailAliasManagerService.getHandleAliases( - connectedAccount, - ); - break; - case ConnectedAccountProvider.IMAP_SMTP_CALDAV: - case ConnectedAccountProvider.OIDC: - case ConnectedAccountProvider.SAML: - case ConnectedAccountProvider.EMAIL_GROUP: - case ConnectedAccountProvider.APP: - handleAliases = []; - break; - default: - assertUnreachable( - connectedAccount.provider, - `Email alias manager for provider ${connectedAccount.provider} is not implemented`, - ); + if (!accountHasMailbox) { + return connectedAccount.handleAliases ?? []; } + const handleAliases = + await this.getHandleAliasesFromProvider(connectedAccount); + const authContext = buildSystemAuthContext(workspaceId); await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => { @@ -67,4 +52,30 @@ export class EmailAliasManagerService { return handleAliases; } + + private async getHandleAliasesFromProvider( + connectedAccount: ConnectedAccountEntity, + ): Promise { + switch (connectedAccount.provider) { + case ConnectedAccountProvider.MICROSOFT: + return this.microsoftEmailAliasManagerService.getHandleAliases( + connectedAccount, + ); + case ConnectedAccountProvider.GOOGLE: + return this.googleEmailAliasManagerService.getHandleAliases( + connectedAccount, + ); + case ConnectedAccountProvider.IMAP_SMTP_CALDAV: + case ConnectedAccountProvider.OIDC: + case ConnectedAccountProvider.SAML: + case ConnectedAccountProvider.EMAIL_GROUP: + case ConnectedAccountProvider.APP: + return []; + default: + return assertUnreachable( + connectedAccount.provider, + `Email alias manager for provider ${connectedAccount.provider} is not implemented`, + ); + } + } }