From 26104d47a61421033122a9108c791aff63a2edee Mon Sep 17 00:00:00 2001 From: Marie <51697796+ijreilly@users.noreply.github.com> Date: Thu, 6 Aug 2026 10:01:34 +0200 Subject: [PATCH] Report a sending domain as pending while SES waits for the DKIM records (#23818) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem image-1785940453654 A sending domain that is simply waiting on its DKIM records is displayed as **Failed**, with every DKIM row marked **Error**, even when the DNS is correct and AWS has already published the key. Hit while setting up `twenty.dev` for a demo. All five CNAMEs resolve correctly from the authoritative nameserver and from a public resolver, none are proxied, the unsubscribe row is green, and the first DKIM token already resolves through to its published key at AWS: ``` $ dig +short TXT abbr…._domainkey.twenty.dev abbr….dkim.amazonses.com. "p=MIIBIjANBgkq…" ``` Yet all three DKIM rows read Error, which tells the user to go fix DNS that isn't broken. ## Cause `determineVerificationStatus` treats `VerifiedForSendingStatus === false` as terminal: ```ts if ( identityResponse.VerifiedForSendingStatus === false || dkimStatus === 'FAILED' ) { return EmailingDomainStatus.FAILED; } return EmailingDomainStatus.PENDING; ``` SES reports `VerifiedForSendingStatus: false` for the entire period it is waiting to detect the DKIM CNAMEs, which is the normal state of every domain between setup and verification. So a pending domain returns FAILED, and the PENDING branch is unreachable for any identity where the field is present at all. `TEMPORARY_FAILURE`, which SES documents as retryable, was also reported as Failed. The status is then stamped onto each DKIM row by `withRecordStatus`, which is why all three rows change together and none of them reflects its own record. --- ...s-ses-driver-domain-status.service.spec.ts | 94 +++++++++++++++++++ .../services/aws-ses-driver.service.ts | 15 ++- 2 files changed, 100 insertions(+), 9 deletions(-) create mode 100644 packages/twenty-server/src/engine/core-modules/emailing-domain/drivers/aws-ses/services/__tests__/aws-ses-driver-domain-status.service.spec.ts diff --git a/packages/twenty-server/src/engine/core-modules/emailing-domain/drivers/aws-ses/services/__tests__/aws-ses-driver-domain-status.service.spec.ts b/packages/twenty-server/src/engine/core-modules/emailing-domain/drivers/aws-ses/services/__tests__/aws-ses-driver-domain-status.service.spec.ts new file mode 100644 index 0000000000..98ee10637f --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/emailing-domain/drivers/aws-ses/services/__tests__/aws-ses-driver-domain-status.service.spec.ts @@ -0,0 +1,94 @@ +import { type AwsSesClientProvider } from 'src/engine/core-modules/emailing-domain/drivers/aws-ses/providers/aws-ses-client.provider'; +import { AwsSesDriver } from 'src/engine/core-modules/emailing-domain/drivers/aws-ses/services/aws-ses-driver.service'; +import { type AwsSesHandleErrorService } from 'src/engine/core-modules/emailing-domain/drivers/aws-ses/services/aws-ses-handle-error.service'; +import { type AwsSesRegisterDomainService } from 'src/engine/core-modules/emailing-domain/drivers/aws-ses/services/aws-ses-register-domain.service'; +import { type AwsSesSendEmailService } from 'src/engine/core-modules/emailing-domain/drivers/aws-ses/services/aws-ses-send-email.service'; +import { type AwsSesDriverConfig } from 'src/engine/core-modules/emailing-domain/drivers/interfaces/driver-config.interface'; +import { EmailingDomainDriver } from 'src/engine/core-modules/emailing-domain/drivers/types/emailing-domain-driver.type'; +import { EmailingDomainStatus } from 'src/engine/core-modules/emailing-domain/drivers/types/emailing-domain-status.type'; +import { type UnsubscribeContentService } from 'src/engine/core-modules/emailing-domain/services/unsubscribe-content.service'; + +describe('AwsSesDriver getDomainStatus', () => { + const config: AwsSesDriverConfig = { + driver: EmailingDomainDriver.AWS_SES, + region: 'us-east-1', + accountId: '123456789012', + }; + + const setUp = (identityResponse: Record) => { + const send = jest.fn().mockResolvedValue(identityResponse); + const clientProvider = { + getSESClient: () => ({ send }), + } as unknown as AwsSesClientProvider; + + const driver = new AwsSesDriver( + config, + clientProvider, + {} as unknown as AwsSesHandleErrorService, + {} as unknown as AwsSesRegisterDomainService, + {} as unknown as AwsSesSendEmailService, + {} as unknown as UnsubscribeContentService, + ); + + return driver.getDomainStatus({ + domain: 'twenty.dev', + workspaceId: 'workspace-id', + }); + }; + + it('should report pending while SES is still waiting for the DKIM records', async () => { + const result = await setUp({ + VerifiedForSendingStatus: false, + DkimAttributes: { SigningEnabled: true, Status: 'PENDING', Tokens: [] }, + }); + + expect(result.status).toBe(EmailingDomainStatus.PENDING); + }); + + it('should report pending when a DKIM check fails temporarily', async () => { + const result = await setUp({ + VerifiedForSendingStatus: false, + DkimAttributes: { + SigningEnabled: true, + Status: 'TEMPORARY_FAILURE', + Tokens: [], + }, + }); + + expect(result.status).toBe(EmailingDomainStatus.PENDING); + }); + + it('should report failed when SES gave up on the DKIM records', async () => { + const result = await setUp({ + VerifiedForSendingStatus: false, + DkimAttributes: { SigningEnabled: true, Status: 'FAILED', Tokens: [] }, + }); + + expect(result.status).toBe(EmailingDomainStatus.FAILED); + }); + + it('should report verified once SES signs with DKIM', async () => { + const result = await setUp({ + VerifiedForSendingStatus: true, + DkimAttributes: { SigningEnabled: true, Status: 'SUCCESS', Tokens: [] }, + }); + + expect(result.status).toBe(EmailingDomainStatus.VERIFIED); + }); + + it('should mark every DKIM record pending while the domain is pending', async () => { + const result = await setUp({ + VerifiedForSendingStatus: false, + DkimAttributes: { + SigningEnabled: true, + Status: 'PENDING', + Tokens: ['token1', 'token2', 'token3'], + }, + }); + + expect(result.verificationRecords).toHaveLength(3); + expect( + result.verificationRecords.every((record) => record.status === 'pending'), + ).toBe(true); + }); +}); diff --git a/packages/twenty-server/src/engine/core-modules/emailing-domain/drivers/aws-ses/services/aws-ses-driver.service.ts b/packages/twenty-server/src/engine/core-modules/emailing-domain/drivers/aws-ses/services/aws-ses-driver.service.ts index ac89c383ac..bd283b637b 100644 --- a/packages/twenty-server/src/engine/core-modules/emailing-domain/drivers/aws-ses/services/aws-ses-driver.service.ts +++ b/packages/twenty-server/src/engine/core-modules/emailing-domain/drivers/aws-ses/services/aws-ses-driver.service.ts @@ -16,16 +16,16 @@ import { import { isNonEmptyString } from '@sniptt/guards'; +import { + EmailingDomainDriverException, + EmailingDomainDriverExceptionCode, +} from 'src/engine/core-modules/emailing-domain/drivers/exceptions/emailing-domain-driver.exception'; import { type AwsSesDriverConfig } from 'src/engine/core-modules/emailing-domain/drivers/interfaces/driver-config.interface'; import { type EmailingDomainDriverInterface, type EmailingDomainResourceInput, type EmailingDomainVerificationResult, } from 'src/engine/core-modules/emailing-domain/drivers/interfaces/emailing-domain-driver.interface'; -import { - EmailingDomainDriverException, - EmailingDomainDriverExceptionCode, -} from 'src/engine/core-modules/emailing-domain/drivers/exceptions/emailing-domain-driver.exception'; import { type EmailingDomainSendEmailRequest } from 'src/engine/core-modules/emailing-domain/drivers/types/emailing-domain-send-email-input.type'; import { type EmailingDomainSendEmailResult } from 'src/engine/core-modules/emailing-domain/drivers/types/emailing-domain-send-email-result.type'; import { UnsubscribeHostnameStatus } from 'src/engine/core-modules/emailing-domain/drivers/types/unsubscribe-hostname-status.type'; @@ -34,8 +34,8 @@ import { type UnsubscribeContentService } from 'src/engine/core-modules/emailing import { AWS_SES_RESOURCE_NAME_PREFIX } from 'src/engine/core-modules/emailing-domain/drivers/aws-ses/constants/aws-ses-resource-name-prefix.constant'; import { type AwsSesClientProvider } from 'src/engine/core-modules/emailing-domain/drivers/aws-ses/providers/aws-ses-client.provider'; -import { AwsSesRegisterDomainService } from 'src/engine/core-modules/emailing-domain/drivers/aws-ses/services/aws-ses-register-domain.service'; import { type AwsSesHandleErrorService } from 'src/engine/core-modules/emailing-domain/drivers/aws-ses/services/aws-ses-handle-error.service'; +import { AwsSesRegisterDomainService } from 'src/engine/core-modules/emailing-domain/drivers/aws-ses/services/aws-ses-register-domain.service'; import { type AwsSesSendEmailService } from 'src/engine/core-modules/emailing-domain/drivers/aws-ses/services/aws-ses-send-email.service'; import { EmailingDomainStatus } from 'src/engine/core-modules/emailing-domain/drivers/types/emailing-domain-status.type'; import { type VerificationRecordDTO } from 'src/engine/core-modules/emailing-domain/dtos/verification-record.dto'; @@ -376,10 +376,7 @@ export class AwsSesDriver implements EmailingDomainDriverInterface { return EmailingDomainStatus.VERIFIED; } - if ( - identityResponse.VerifiedForSendingStatus === false || - dkimStatus === 'FAILED' - ) { + if (dkimStatus === 'FAILED') { return EmailingDomainStatus.FAILED; }