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:
+94
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
+6
-9
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user