feat(emailing-domain): add LOG driver for local development (#21286)

## What

Adds a `LOG` driver to the emailing-domain feature, selected via a new
`EMAILING_DOMAIN_DRIVER` config variable (defaults to `AWS_SES`, so
production behavior is unchanged).

The LOG driver:
- resolves domains to `VERIFIED` instantly (no DNS / SES setup)
- logs each `sendEmail` and returns a synthetic `messageId` instead of
calling SES

It also dev-seeds a pre-verified domain per workspace
(`<workspaceId>.dev.twenty.local`) so the feature works out of the box.

## Why

The emailing-domain feature currently ships only the AWS SES driver, so
the verify → send flow can't be exercised locally (or in CI) without
real AWS credentials. This unblocks local development and review of
anything built on emailing domains.

## Usage

```
EMAILING_DOMAIN_DRIVER=LOG
```

The seeded `*.dev.twenty.local` domain is already verified; sends are
logged (`[log-driver] sendEmail ...`).

---------

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
This commit is contained in:
Félix Malfait
2026-06-08 14:59:11 +02:00
committed by GitHub
parent b1b030c894
commit dfb3da1f8d
22 changed files with 724 additions and 608 deletions
@@ -0,0 +1,46 @@
import { type QueryRunner } from 'typeorm';
import { EmailingDomainStatus } from 'src/engine/core-modules/emailing-domain/drivers/types/emailing-domain-status.type';
import { EmailingDomainTenantStatus } from 'src/engine/core-modules/emailing-domain/drivers/types/emailing-domain-tenant-status.type';
const tableName = 'emailingDomain';
const DEV_EMAILING_DOMAIN = 'dev.twenty.local';
type SeedEmailingDomainsArgs = {
queryRunner: QueryRunner;
schemaName: string;
workspaceId: string;
};
export const seedEmailingDomains = async ({
queryRunner,
schemaName,
workspaceId,
}: SeedEmailingDomainsArgs) => {
const domain = `${workspaceId.slice(0, 8)}.${DEV_EMAILING_DOMAIN}`;
await queryRunner.manager
.createQueryBuilder()
.insert()
.into(`${schemaName}.${tableName}`, [
'workspaceId',
'domain',
'status',
'verificationRecords',
'verifiedAt',
'tenantStatus',
])
.orIgnore()
.values([
{
workspaceId,
domain,
status: EmailingDomainStatus.VERIFIED,
verificationRecords: JSON.stringify([]),
verifiedAt: new Date(),
tenantStatus: EmailingDomainTenantStatus.ACTIVE,
},
])
.execute();
};
@@ -6,6 +6,7 @@ import { v4 } from 'uuid';
import { ApplicationRegistrationService } from 'src/engine/core-modules/application/application-registration/application-registration.service';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { EmailingDomainDriver } from 'src/engine/core-modules/emailing-domain/drivers/types/emailing-domain-driver.type';
import { SdkClientGenerationService } from 'src/engine/core-modules/sdk-client/sdk-client-generation.service';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { UpgradeMigrationService } from 'src/engine/core-modules/upgrade/services/upgrade-migration.service';
@@ -27,6 +28,7 @@ import {
import { DevSeederPermissionsService } from 'src/engine/workspace-manager/dev-seeder/core/services/dev-seeder-permissions.service';
import { seedAgents } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-agents.util';
import { seedApiKeys } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-api-keys.util';
import { seedEmailingDomains } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-emailing-domains.util';
import { seedFeatureFlags } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util';
import { seedMetadataEntities } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-metadata-entities.util';
import { seedPageLayouts } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-page-layouts.util';
@@ -319,6 +321,12 @@ export class DevSeederService {
await seedAgents({ queryRunner, schemaName, workspaceId });
await seedApiKeys({ queryRunner, schemaName, workspaceId });
if (
this.twentyConfigService.get('EMAILING_DOMAIN_DRIVER') ===
EmailingDomainDriver.LOG
) {
await seedEmailingDomains({ queryRunner, schemaName, workspaceId });
}
await seedFeatureFlags({ queryRunner, schemaName, workspaceId });
if (seedBilling) {