fix: clean up email attachment files after send and move migration to 1-22

Delete temporary email attachment files from storage after a successful
send to prevent file storage leak. Also move the
AddSendEmailRecordSelectionCommandMenuItems upgrade command from 1-21
to 1-22 since 1-21 has already been released.

https://claude.ai/code/session_014DjujrbcZu4WrdhULUR8CH
This commit is contained in:
Claude 2026-04-10 07:46:06 +00:00
parent 9bf6d9afee
commit 1ae759a089
No known key found for this signature in database
6 changed files with 42 additions and 5 deletions

View file

@ -16,7 +16,6 @@ import { MigrateMessageFolderParentIdToExternalIdCommand } from 'src/database/co
import { MigrateMessagingInfrastructureToMetadataCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-workspace-command-1775500012000-migrate-messaging-infrastructure-to-metadata.command';
import { FixMessageThreadViewAndLabelIdentifierCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-workspace-command-1775500014000-fix-message-thread-view-and-label-identifier.command';
import { UpdateSearchCommandMenuItemLabelsCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-workspace-command-1775500015000-update-search-command-menu-item-labels.command';
import { AddSendEmailRecordSelectionCommandMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-workspace-command-1775500016000-add-send-email-record-selection-command-menu-items.command';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
@ -67,7 +66,6 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
MigrateMessagingInfrastructureToMetadataCommand,
FixMessageThreadViewAndLabelIdentifierCommand,
UpdateSearchCommandMenuItemLabelsCommand,
AddSendEmailRecordSelectionCommandMenuItemsCommand,
],
})
export class V1_21_UpgradeVersionCommandModule {}

View file

@ -3,6 +3,7 @@ import { Module } from '@nestjs/common';
import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module';
import { BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand } from 'src/database/commands/upgrade-version-command/1-22/1-22-workspace-command-1780000001000-backfill-page-layouts-and-fields-widget-view-fields.command';
import { BackfillStandardSkillsCommand } from 'src/database/commands/upgrade-version-command/1-22/1-22-workspace-command-1780000002000-backfill-standard-skills.command';
import { AddSendEmailRecordSelectionCommandMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-22/1-22-workspace-command-1775500016000-add-send-email-record-selection-command-menu-items.command';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
@ -17,6 +18,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
WorkspaceMigrationModule,
],
providers: [
AddSendEmailRecordSelectionCommandMenuItemsCommand,
BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
BackfillStandardSkillsCommand,
],

View file

@ -17,9 +17,9 @@ const SEND_EMAIL_RECORD_SELECTION_UNIVERSAL_IDENTIFIERS = [
STANDARD_COMMAND_MENU_ITEMS.composeEmailToOpportunity.universalIdentifier,
];
@RegisteredWorkspaceCommand('1.21.0', 1775500016000)
@RegisteredWorkspaceCommand('1.22.0', 1775500016000)
@Command({
name: 'upgrade:1-21:add-send-email-record-selection-command-menu-items',
name: 'upgrade:1-22:add-send-email-record-selection-command-menu-items',
description:
'Add the per-object Send Email command menu items (Person, Company, Opportunity) to existing workspaces',
})

View file

@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common';
import { Injectable, Logger } from '@nestjs/common';
import { isNonEmptyString } from '@sniptt/guards';
import { FileFolder } from 'twenty-shared/types';
@ -13,6 +13,8 @@ import { sanitizeFile } from 'src/engine/core-modules/file/utils/sanitize-file.u
@Injectable()
export class FileEmailAttachmentService {
private readonly logger = new Logger(FileEmailAttachmentService.name);
constructor(
private readonly fileStorageService: FileStorageService,
private readonly applicationService: ApplicationService,
@ -69,4 +71,26 @@ export class FileEmailAttachmentService {
}),
};
}
async deleteFiles({
fileIds,
workspaceId,
}: {
fileIds: string[];
workspaceId: string;
}): Promise<void> {
for (const fileId of fileIds) {
try {
await this.fileStorageService.deleteByFileId({
fileId,
workspaceId,
fileFolder: FileFolder.EmailAttachment,
});
} catch (error) {
this.logger.warn(
`Failed to delete email attachment file ${fileId}: ${error}`,
);
}
}
}
}

View file

@ -11,6 +11,7 @@ import { FileFolder } from 'twenty-shared/types';
import { MetadataResolver } from 'src/engine/api/graphql/graphql-config/decorators/metadata-resolver.decorator';
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
import { FileEmailAttachmentService } from 'src/engine/core-modules/file/file-email-attachment/services/file-email-attachment.service';
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { EmailComposerService } from 'src/engine/core-modules/tool/tools/email-tool/email-composer.service';
@ -33,6 +34,7 @@ export class SendEmailResolver {
constructor(
private readonly connectedAccountMetadataService: ConnectedAccountMetadataService,
private readonly emailComposerService: EmailComposerService,
private readonly fileEmailAttachmentService: FileEmailAttachmentService,
private readonly sendEmailService: SendEmailService,
) {}
@ -83,6 +85,15 @@ export class SendEmailResolver {
workspace.id,
);
const attachmentFileIds = (input.files ?? []).map((file) => file.id);
if (attachmentFileIds.length > 0) {
await this.fileEmailAttachmentService.deleteFiles({
fileIds: attachmentFileIds,
workspaceId: workspace.id,
});
}
return { success: true };
} catch (error) {
if (error instanceof ForbiddenException) {

View file

@ -1,5 +1,6 @@
import { Module } from '@nestjs/common';
import { FileEmailAttachmentModule } from 'src/engine/core-modules/file/file-email-attachment/file-email-attachment.module';
import { ToolModule } from 'src/engine/core-modules/tool/tool.module';
import { ConnectedAccountMetadataModule } from 'src/engine/metadata-modules/connected-account/connected-account-metadata.module';
import { SendEmailResolver } from 'src/modules/messaging/message-outbound-manager/resolvers/send-email.resolver';
@ -7,6 +8,7 @@ import { MessagingSendManagerModule } from 'src/modules/messaging/message-outbou
@Module({
imports: [
FileEmailAttachmentModule,
ToolModule,
MessagingSendManagerModule,
ConnectedAccountMetadataModule,