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,30 @@
import { type QueryRunner } from 'typeorm';
import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
import { type FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface';
// The emailing domain driver is selected globally via the EMAILING_DOMAIN_DRIVER
// config variable; the per-row column was never read and is redundant.
@RegisteredInstanceCommand('2.11.0', 1780926908000)
export class DropEmailingDomainDriverColumnFastInstanceCommand implements FastInstanceCommand {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "core"."emailingDomain" DROP COLUMN IF EXISTS "driver"`,
);
await queryRunner.query(
`DROP TYPE IF EXISTS "core"."emailingDomain_driver_enum"`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TYPE "core"."emailingDomain_driver_enum" AS ENUM('AWS_SES')`,
);
await queryRunner.query(
`ALTER TABLE "core"."emailingDomain" ADD COLUMN IF NOT EXISTS "driver" "core"."emailingDomain_driver_enum" NOT NULL DEFAULT 'AWS_SES'`,
);
await queryRunner.query(
`ALTER TABLE "core"."emailingDomain" ALTER COLUMN "driver" DROP DEFAULT`,
);
}
}
@@ -59,6 +59,7 @@ import { EmailingDomainTenantStatusAndGlobalUniquenessFastInstanceCommand } from
import { AddLogicFunctionExecutionModeFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-9/2-9-instance-command-fast-1799000030000-add-logic-function-execution-mode';
import { EncryptNonSecretApplicationVariableSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-9/2-9-instance-command-slow-1798400000000-encrypt-non-secret-application-variable';
import { MigrateAiModelPreferencesSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-9/2-9-instance-command-slow-1799000010000-migrate-ai-model-preferences';
import { DropEmailingDomainDriverColumnFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-11/2-11-instance-command-fast-1780926908000-drop-emailing-domain-driver-column';
export const INSTANCE_COMMANDS = [
AddViewFieldGroupIdIndexOnViewFieldFastInstanceCommand,
@@ -120,4 +121,5 @@ export const INSTANCE_COMMANDS = [
AddLogicFunctionExecutionModeFastInstanceCommand,
MigrateAiModelPreferencesSlowInstanceCommand,
EncryptNonSecretApplicationVariableSlowInstanceCommand,
DropEmailingDomainDriverColumnFastInstanceCommand,
];