feat: importierte E-Mails automatisch in IMAP-Ordner verschieben und nach 90 Tagen löschen
Build and Push Multi-Platform Images / build-and-push (push) Successful in 31s

- Neuer ImapFolderService verschiebt E-Mails nach erfolgreichem Import in den
  konfigurierbaren Ordner "importiert" (wird bei Bedarf automatisch erstellt)
- Täglicher Cron um 03:00 Uhr verschiebt E-Mails älter als 90 Tage in den
  Papierkorb und leert ihn anschließend
- createImapClient()-Hilfsmethode im EmailDownloadService ausgelagert
- IMAP_IMPORTED_FOLDER und IMAP_TRASH_FOLDER in docker-compose ergänzt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-16 21:58:41 +02:00
co-authored by Claude Sonnet 4.6
parent ef7813f9f9
commit ed57477324
5 changed files with 120 additions and 12 deletions
@@ -12,6 +12,7 @@ import { Task } from '../database/entities/task.entity';
import { PaperlessService } from '../paperless/paperless.service';
import * as QRCode from 'qrcode';
import { EmailPageCacheService } from './email-page-cache.service';
import { ImapFolderService } from './imap-folder.service';
import { PdfService } from '../preprocessing/pdf.service';
import * as path from 'path';
import * as os from 'os';
@@ -52,6 +53,7 @@ export class EmailImportService {
private readonly paperlessService: PaperlessService,
private readonly pdfService: PdfService,
private readonly pageCache: EmailPageCacheService,
private readonly imapFolderService: ImapFolderService,
) {}
async ensurePreviews(emailId: number): Promise<void> {
@@ -646,6 +648,12 @@ export class EmailImportService {
this.logger.log(
`Email ${firstAtt.EmailMessageId} als verarbeitet markiert.`,
);
const emailEntity = await this.emailRepo.findOne({ where: { Id: firstAtt.EmailMessageId } });
if (emailEntity) {
this.imapFolderService.moveToImportiert(emailEntity.MessageId).catch(err =>
this.logger.error('IMAP-Verschieben fehlgeschlagen: ' + err.message),
);
}
}
}
+2 -1
View File
@@ -9,6 +9,7 @@ import { EmailPageCacheService } from './email-page-cache.service';
import { EmailImportController } from './email-import.controller';
import { EmailImportService } from './email-import.service';
import { ImapFolderService } from './imap-folder.service';
import { CorrespondentEmailMapping } from '../database/entities/correspondent-email-mapping.entity';
import { Task } from '../database/entities/task.entity';
import { PreprocessingModule } from '../preprocessing/preprocessing.module';
@@ -26,7 +27,7 @@ import { PreprocessingModule } from '../preprocessing/preprocessing.module';
PreprocessingModule,
],
controllers: [EmailController, EmailImportController],
providers: [EmailImportService, EmailPageCacheService],
providers: [EmailImportService, EmailPageCacheService, ImapFolderService],
exports: [EmailPageCacheService],
})
export class EmailModule {}
@@ -0,0 +1,52 @@
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ImapFlow } from 'imapflow';
@Injectable()
export class ImapFolderService {
private readonly logger = new Logger(ImapFolderService.name);
constructor(private readonly configService: ConfigService) {}
private createClient(): ImapFlow {
return new ImapFlow({
host: this.configService.get<string>('IMAP_HOST', ''),
port: this.configService.get<number>('IMAP_PORT', 993),
secure: this.configService.get<string>('IMAP_USE_SSL', 'true') === 'true',
auth: {
user: this.configService.get<string>('IMAP_USERNAME', ''),
pass: this.configService.get<string>('IMAP_PASSWORD', ''),
},
logger: false,
});
}
async moveToImportiert(messageId: string): Promise<void> {
if (!this.configService.get<string>('IMAP_HOST')) return;
const importedFolder = this.configService.get<string>('IMAP_IMPORTED_FOLDER', 'importiert');
const client = this.createClient();
try {
await client.connect();
const mailboxes = await client.list();
if (!mailboxes.some(m => m.path === importedFolder)) {
await client.mailboxCreate(importedFolder);
this.logger.log(`IMAP-Ordner "${importedFolder}" erstellt.`);
}
await client.mailboxOpen('INBOX');
const uids = await client.search({ header: { 'message-id': messageId } }, { uid: true });
if (Array.isArray(uids) && uids.length > 0) {
await client.messageMove(uids, importedFolder, { uid: true });
this.logger.log(`E-Mail ${messageId} → "${importedFolder}" verschoben.`);
} else {
this.logger.warn(`E-Mail ${messageId} nicht in INBOX gefunden (bereits verschoben?).`);
}
} catch (err: any) {
this.logger.error(`IMAP moveToImportiert fehlgeschlagen: ${err.message}`);
} finally {
await client.logout().catch(() => {});
}
}
}