Report a sending domain as pending while SES waits for the DKIM records (#23818)

## Problem
<img width="648" height="310" alt="image-1785940453654"
src="https://github.com/user-attachments/assets/60362b8c-fed5-4ce3-af45-9aa064356f11"
/>

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.
This commit is contained in:
Marie
2026-08-06 10:01:34 +02:00
committed by GitHub
parent 299ebb1890
commit 26104d47a6
2 changed files with 100 additions and 9 deletions
@@ -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<string, unknown>) => {
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);
});
});
@@ -16,16 +16,16 @@ import {
import { isNonEmptyString } from '@sniptt/guards'; 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 AwsSesDriverConfig } from 'src/engine/core-modules/emailing-domain/drivers/interfaces/driver-config.interface';
import { import {
type EmailingDomainDriverInterface, type EmailingDomainDriverInterface,
type EmailingDomainResourceInput, type EmailingDomainResourceInput,
type EmailingDomainVerificationResult, type EmailingDomainVerificationResult,
} from 'src/engine/core-modules/emailing-domain/drivers/interfaces/emailing-domain-driver.interface'; } 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 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 { 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'; 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 { 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 { 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 { 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 { 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 { 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'; 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; return EmailingDomainStatus.VERIFIED;
} }
if ( if (dkimStatus === 'FAILED') {
identityResponse.VerifiedForSendingStatus === false ||
dkimStatus === 'FAILED'
) {
return EmailingDomainStatus.FAILED; return EmailingDomainStatus.FAILED;
} }