// ==================== IMAP SERVICE ==================== // Service für IMAP-Zugriff auf Mailboxen import { ImapFlow, FetchMessageObject } from 'imapflow'; import { simpleParser, ParsedMail, AddressObject } from 'mailparser'; // Verschlüsselungstyp export type MailEncryption = 'SSL' | 'STARTTLS' | 'NONE'; export interface ImapCredentials { host: string; port: number; user: string; password: string; encryption?: MailEncryption; // SSL, STARTTLS oder NONE (Standard: SSL) allowSelfSignedCerts?: boolean; // Selbstsignierte Zertifikate erlauben } /** * TLS-Optionen für IMAP-Verbindungen zusammenbauen. * Wenn `allowSelfSignedCerts` aktiv ist, werden zusätzlich ältere TLS-Versionen * (TLS 1.0+) und legacy Cipher-Suites erlaubt – hilfreich bei älteren Mailservern, * die sonst den Socket sofort nach Connect schließen. */ function buildTlsOptions(credentials: ImapCredentials): Record | undefined { const encryption = credentials.encryption ?? 'SSL'; if (encryption === 'NONE') return undefined; const rejectUnauthorized = !credentials.allowSelfSignedCerts; const options: Record = { rejectUnauthorized }; if (credentials.allowSelfSignedCerts) { options.minVersion = 'TLSv1'; options.ciphers = 'DEFAULT:@SECLEVEL=0'; } return options; } export interface FetchedEmail { uid: number; messageId: string; subject: string | null; fromAddress: string; fromName: string | null; toAddresses: string[]; ccAddresses: string[]; date: Date; textBody: string | null; htmlBody: string | null; hasAttachments: boolean; attachmentNames: string[]; } export interface FetchOptions { folder?: string; // Default: 'INBOX' since?: Date; // Nur E-Mails nach diesem Datum limit?: number; // Max. Anzahl E-Mails sinceUid?: number; // Nur E-Mails ab dieser UID (für inkrementellen Sync) } // Helper: Adressen aus mailparser-Format extrahieren function extractAddresses(addressObj: AddressObject | AddressObject[] | undefined): string[] { if (!addressObj) return []; const addresses = Array.isArray(addressObj) ? addressObj : [addressObj]; const result: string[] = []; for (const obj of addresses) { if (obj.value) { for (const addr of obj.value) { if (addr.address) { result.push(addr.address); } } } } return result; } // Helper: Ersten Absender-Namen extrahieren function extractFromName(addressObj: AddressObject | AddressObject[] | undefined): string | null { if (!addressObj) return null; const addresses = Array.isArray(addressObj) ? addressObj : [addressObj]; for (const obj of addresses) { if (obj.value && obj.value[0]) { return obj.value[0].name || null; } } return null; } // E-Mails aus einer Mailbox abrufen export async function fetchEmails( credentials: ImapCredentials, options: FetchOptions = {} ): Promise { const { folder = 'INBOX', since, limit = 50, sinceUid, } = options; // Verschlüsselungs-Einstellungen basierend auf Modus const encryption = credentials.encryption ?? 'SSL'; const rejectUnauthorized = !credentials.allowSelfSignedCerts; // ImapFlow-Optionen je nach Verschlüsselungstyp // SSL: secure=true (implicit TLS, Port 993) // STARTTLS: secure=false (upgrades to TLS, Port 143) // NONE: secure=false + disableAutoIdle (no encryption, Port 143) const clientOptions: ConstructorParameters[0] = { host: credentials.host, port: credentials.port, secure: encryption === 'SSL', auth: { user: credentials.user, pass: credentials.password, }, logger: false, }; // TLS-Optionen nur wenn nicht NONE if (encryption !== 'NONE') { const __tls = buildTlsOptions(credentials); if (__tls) clientOptions.tls = __tls as any; } // Debug-Logging console.log(`[IMAP] Connecting to ${credentials.host}:${credentials.port} (${encryption}), user: ${credentials.user}`); const client = new ImapFlow(clientOptions); const emails: FetchedEmail[] = []; try { await client.connect(); console.log(`[IMAP] Connected successfully`); // Mailbox öffnen await client.mailboxOpen(folder); // Suchkriterien zusammenstellen let searchCriteria: { since?: Date; uid?: string } = {}; if (since) { searchCriteria.since = since; } // IMAP SEARCH ausführen let uids: number[]; if (sinceUid) { // Inkrementeller Sync: Nur E-Mails ab einer bestimmten UID // IMAP UID-Range: "sinceUid:*" bedeutet alle E-Mails >= sinceUid const messages = await client.search({ uid: `${sinceUid}:*` }, { uid: true }); const messageArray = Array.isArray(messages) ? messages : []; uids = messageArray.filter((uid: number) => uid > sinceUid); // Exkludiere die sinceUid selbst } else if (since) { // Nach Datum suchen const messages = await client.search({ since }, { uid: true }); uids = Array.isArray(messages) ? messages : []; } else { // Alle E-Mails (mit Limit) const messages = await client.search({ all: true }, { uid: true }); uids = Array.isArray(messages) ? messages : []; } // Neueste zuerst (absteigend sortieren) uids.sort((a, b) => b - a); // Limit anwenden const limitedUids = uids.slice(0, limit); console.log(`[IMAP] Found ${uids.length} emails, fetching ${limitedUids.length}`); if (limitedUids.length === 0) { console.log(`[IMAP] No emails to fetch`); await client.logout(); return []; } // E-Mails abrufen - drittes Argument { uid: true } für UID FETCH for await (const message of client.fetch(limitedUids.join(','), { uid: true, // UID im Response inkludieren envelope: true, source: true, // Vollständige E-Mail für Parsing }, { uid: true })) { try { // Source muss vorhanden sein if (!message.source) { console.error(`E-Mail UID ${message.uid} hat keine Source`); continue; } // E-Mail mit mailparser parsen const parsed = await simpleParser(message.source) as ParsedMail; const email: FetchedEmail = { uid: message.uid, messageId: parsed.messageId || `${message.uid}@unknown`, subject: parsed.subject || null, fromAddress: extractAddresses(parsed.from)[0] || 'unknown@unknown', fromName: extractFromName(parsed.from), toAddresses: extractAddresses(parsed.to), ccAddresses: extractAddresses(parsed.cc), date: parsed.date || new Date(), textBody: parsed.text || null, htmlBody: parsed.html ? String(parsed.html) : null, hasAttachments: (parsed.attachments?.length || 0) > 0, attachmentNames: parsed.attachments?.map((a) => a.filename || 'unnamed') || [], }; emails.push(email); } catch (parseError) { console.error(`Fehler beim Parsen von E-Mail UID ${message.uid}:`, parseError); // E-Mail überspringen bei Parse-Fehlern } } await client.logout(); } catch (error) { // Verbindung sauber schließen bei Fehlern try { await client.logout(); } catch { // Ignorieren } // Bessere Fehlermeldungen if (error instanceof Error) { const msg = error.message.toLowerCase(); const errorCode = (error as NodeJS.ErrnoException).code?.toLowerCase() || ''; if (msg.includes('authentication') || msg.includes('login')) { throw new Error('IMAP-Authentifizierung fehlgeschlagen - Zugangsdaten prüfen'); } if (msg.includes('econnrefused') || errorCode === 'econnrefused') { throw new Error(`IMAP-Server nicht erreichbar: ${credentials.host}:${credentials.port} - Verbindung verweigert`); } if (msg.includes('timeout') || msg.includes('etimedout') || errorCode === 'etimedout') { const enc = credentials.encryption ?? 'SSL'; if (enc === 'STARTTLS' && credentials.port === 143) { throw new Error(`IMAP Port 143 (STARTTLS) nicht erreichbar auf ${credentials.host}`); } else if (enc === 'SSL' && credentials.port === 993) { throw new Error(`IMAP Port 993 (SSL) nicht erreichbar auf ${credentials.host}`); } else { throw new Error(`IMAP-Verbindung zu ${credentials.host}:${credentials.port} fehlgeschlagen - Timeout`); } } if (msg.includes('certificate') || msg.includes('cert')) { throw new Error('IMAP SSL-Zertifikatfehler - Aktiviere "Selbstsignierte Zertifikate erlauben"'); } } throw error; } return emails; } // Verbindung testen export async function testImapConnection(credentials: ImapCredentials): Promise { // Verschlüsselungs-Einstellungen basierend auf Modus const encryption = credentials.encryption ?? 'SSL'; const rejectUnauthorized = !credentials.allowSelfSignedCerts; const clientOptions: ConstructorParameters[0] = { host: credentials.host, port: credentials.port, secure: encryption === 'SSL', auth: { user: credentials.user, pass: credentials.password, }, logger: false, }; if (encryption !== 'NONE') { const __tls = buildTlsOptions(credentials); if (__tls) clientOptions.tls = __tls as any; } const client = new ImapFlow(clientOptions); try { await client.connect(); await client.logout(); } catch (error) { // Verbindung sauber schließen bei Fehlern try { await client.logout(); } catch { // Ignorieren } // Rohes Error-Objekt loggen, damit wir ImapFlow-spezifische Felder sehen console.error('[testImapConnection] Raw error:', error); if (error && typeof error === 'object') { const e = error as any; console.error('[testImapConnection] Details:', { code: e.code, response: e.response, responseStatus: e.responseStatus, responseText: e.responseText, authenticationFailed: e.authenticationFailed, serverResponseCode: e.serverResponseCode, }); } if (error instanceof Error) { const msg = error.message.toLowerCase(); const errorCode = (error as NodeJS.ErrnoException).code?.toLowerCase() || ''; const e = error as any; // ImapFlow-spezifische Details durchreichen wenn vorhanden if (e.authenticationFailed) { throw new Error( `IMAP-Authentifizierung fehlgeschlagen${e.response ? `: ${e.response}` : ''}`, ); } if (e.response || e.responseText) { throw new Error( `IMAP ${e.responseStatus || 'Fehler'}: ${e.response || e.responseText}`, ); } if (msg.includes('authentication') || msg.includes('login')) { throw new Error('IMAP-Authentifizierung fehlgeschlagen'); } if (msg.includes('econnrefused') || errorCode === 'econnrefused') { throw new Error(`IMAP-Server nicht erreichbar: ${credentials.host}:${credentials.port} - Verbindung verweigert`); } if (msg.includes('timeout') || msg.includes('etimedout') || errorCode === 'etimedout') { if (encryption === 'STARTTLS' && credentials.port === 143) { throw new Error(`IMAP Port 143 (STARTTLS) nicht erreichbar auf ${credentials.host}`); } else if (encryption === 'SSL' && credentials.port === 993) { throw new Error(`IMAP Port 993 (SSL) nicht erreichbar auf ${credentials.host}`); } else { throw new Error(`IMAP-Verbindung zu ${credentials.host}:${credentials.port} fehlgeschlagen - Timeout`); } } if (msg.includes('certificate') || msg.includes('cert')) { throw new Error('IMAP SSL-Zertifikatfehler - Aktiviere "Selbstsignierte Zertifikate erlauben"'); } } throw error; } } // Höchste UID in einer Mailbox ermitteln (für inkrementellen Sync) export async function getHighestUid( credentials: ImapCredentials, folder: string = 'INBOX' ): Promise { // Verschlüsselungs-Einstellungen basierend auf Modus const encryption = credentials.encryption ?? 'SSL'; const rejectUnauthorized = !credentials.allowSelfSignedCerts; const clientOptions: ConstructorParameters[0] = { host: credentials.host, port: credentials.port, secure: encryption === 'SSL', auth: { user: credentials.user, pass: credentials.password, }, logger: false, }; if (encryption !== 'NONE') { const __tls = buildTlsOptions(credentials); if (__tls) clientOptions.tls = __tls as any; } const client = new ImapFlow(clientOptions); try { await client.connect(); const mailbox = await client.mailboxOpen(folder); const highestUid = mailbox.uidNext ? mailbox.uidNext - 1 : 0; await client.logout(); return highestUid; } catch (error) { try { await client.logout(); } catch { // Ignorieren } throw error; } } // Anhang-Interface export interface EmailAttachmentData { filename: string; content: Buffer; contentType: string; size: number; } // Anhang einer E-Mail per UID abrufen export async function fetchAttachment( credentials: ImapCredentials, uid: number, attachmentFilename: string, folder: string = 'INBOX' ): Promise { // Bei transienten Netzwerkfehlern automatisch bis zu 2x retry let lastError: unknown; for (let attempt = 1; attempt <= 3; attempt++) { try { return await fetchAttachmentInner(credentials, uid, attachmentFilename, folder); } catch (err) { lastError = err; const msg = err instanceof Error ? err.message.toLowerCase() : ''; const isTransient = msg.includes('socket disconnected') || msg.includes('econnreset') || msg.includes('etimedout') || msg.includes('socket hang up') || msg.includes('network socket'); if (!isTransient || attempt === 3) { throw err; } console.warn( `[fetchAttachment] Versuch ${attempt}/3 fehlgeschlagen (transient), retry in ${attempt * 500}ms:`, msg, ); await new Promise((r) => setTimeout(r, attempt * 500)); } } throw lastError; } async function fetchAttachmentInner( credentials: ImapCredentials, uid: number, attachmentFilename: string, folder: string = 'INBOX' ): Promise { // Verschlüsselungs-Einstellungen basierend auf Modus const encryption = credentials.encryption ?? 'SSL'; const rejectUnauthorized = !credentials.allowSelfSignedCerts; const clientOptions: ConstructorParameters[0] = { host: credentials.host, port: credentials.port, secure: encryption === 'SSL', auth: { user: credentials.user, pass: credentials.password, }, logger: false, // Timeouts gegen hängende Verbindungen socketTimeout: 30000, }; if (encryption !== 'NONE') { const __tls = buildTlsOptions(credentials); if (__tls) clientOptions.tls = __tls as any; } const client = new ImapFlow(clientOptions); console.log(`[fetchAttachment] Host: ${credentials.host}:${credentials.port} | User: ${credentials.user} | Folder: ${folder} | UID: ${uid} | File: ${attachmentFilename} | AllowSelfSigned: ${credentials.allowSelfSignedCerts}`); try { await client.connect(); // Ordner öffnen – bei Fehler alle verfügbaren Ordner listen für Debugging try { await client.mailboxOpen(folder); } catch (folderErr) { console.error(`[fetchAttachment] mailboxOpen('${folder}') failed:`, folderErr); try { const list = await client.list(); const available = list.map((m) => m.path).join(', '); console.error(`[fetchAttachment] Verfügbare Ordner: ${available}`); throw new Error( `Ordner '${folder}' nicht gefunden. Verfügbar: ${available}`, ); } catch (listErr) { throw folderErr; } } // E-Mail per UID abrufen let attachment: EmailAttachmentData | null = null; let foundMessage = false; // Drittes Argument { uid: true } sorgt dafür, dass UID FETCH statt FETCH verwendet wird try { for await (const message of client.fetch(uid.toString(), { source: true, }, { uid: true })) { foundMessage = true; if (!message.source) continue; // E-Mail parsen const parsed = await simpleParser(message.source); // Anhang suchen if (parsed.attachments) { for (const att of parsed.attachments) { const filename = att.filename || 'unnamed'; if (filename === attachmentFilename) { attachment = { filename, content: att.content, contentType: att.contentType || 'application/octet-stream', size: att.size, }; break; } } } } } catch (fetchErr) { console.error(`[fetchAttachment] fetch(UID ${uid}) failed:`, fetchErr); throw new Error( `Nachricht mit UID ${uid} konnte nicht geladen werden (${fetchErr instanceof Error ? fetchErr.message : 'unbekannter Fehler'}). Möglicherweise wurde sie im IMAP-Postfach verschoben oder gelöscht.`, ); } if (!foundMessage) { console.warn(`[fetchAttachment] Keine Nachricht mit UID ${uid} in Ordner '${folder}' gefunden`); } await client.logout(); return attachment; } catch (error) { try { await client.logout(); } catch { // Ignorieren } throw error; } } // Gesendete E-Mail im IMAP Sent-Ordner speichern (für Attachment-Download) export interface AppendToSentParams { rawEmail: Buffer | string; // RFC 5322 formatierte E-Mail sentFolder?: string; // Standard: 'Sent' } export interface AppendToSentResult { success: boolean; uid?: number; // UID der gespeicherten Nachricht error?: string; } export async function appendToSent( credentials: ImapCredentials, params: AppendToSentParams ): Promise { const { rawEmail, sentFolder = 'Sent' } = params; const encryption = credentials.encryption ?? 'SSL'; const rejectUnauthorized = !credentials.allowSelfSignedCerts; const clientOptions: ConstructorParameters[0] = { host: credentials.host, port: credentials.port, secure: encryption === 'SSL', auth: { user: credentials.user, pass: credentials.password, }, logger: false, }; if (encryption !== 'NONE') { const __tls = buildTlsOptions(credentials); if (__tls) clientOptions.tls = __tls as any; } console.log(`[IMAP] Appending email to ${sentFolder} folder...`); const client = new ImapFlow(clientOptions); try { await client.connect(); // E-Mail im Sent-Ordner speichern const result = await client.append(sentFolder, rawEmail, ['\\Seen'], new Date()); await client.logout(); // append kann false zurückgeben bei Fehler if (!result) { return { success: false, error: 'IMAP append fehlgeschlagen' }; } console.log(`[IMAP] Email appended successfully, UID: ${result.uid}`); return { success: true, uid: result.uid, }; } catch (error) { try { await client.logout(); } catch { // Ignorieren } console.error('[IMAP] Error appending to Sent folder:', error); // Versuche mit alternativen Ordnernamen falls 'Sent' nicht existiert if (sentFolder === 'Sent' && error instanceof Error) { // Typische alternative Namen für Sent-Ordner const alternativeNames = ['INBOX.Sent', 'Sent Messages', 'Sent Items']; for (const altFolder of alternativeNames) { try { const altClient = new ImapFlow(clientOptions); await altClient.connect(); const altResult = await altClient.append(altFolder, rawEmail, ['\\Seen'], new Date()); await altClient.logout(); if (altResult) { console.log(`[IMAP] Email appended to ${altFolder}, UID: ${altResult.uid}`); return { success: true, uid: altResult.uid }; } } catch { // Nächsten Namen versuchen } } } return { success: false, error: error instanceof Error ? error.message : 'Fehler beim Speichern im Sent-Ordner', }; } } // Alle Anhänge einer E-Mail per UID abrufen (Metadaten) export async function fetchAttachmentList( credentials: ImapCredentials, uid: number, folder: string = 'INBOX' ): Promise> { // Verschlüsselungs-Einstellungen basierend auf Modus const encryption = credentials.encryption ?? 'SSL'; const rejectUnauthorized = !credentials.allowSelfSignedCerts; const clientOptions: ConstructorParameters[0] = { host: credentials.host, port: credentials.port, secure: encryption === 'SSL', auth: { user: credentials.user, pass: credentials.password, }, logger: false, }; if (encryption !== 'NONE') { const __tls = buildTlsOptions(credentials); if (__tls) clientOptions.tls = __tls as any; } const client = new ImapFlow(clientOptions); const attachments: Array<{ filename: string; contentType: string; size: number }> = []; try { await client.connect(); await client.mailboxOpen(folder); // Drittes Argument { uid: true } für UID FETCH for await (const message of client.fetch(uid.toString(), { source: true, }, { uid: true })) { if (!message.source) continue; const parsed = await simpleParser(message.source); if (parsed.attachments) { for (const att of parsed.attachments) { attachments.push({ filename: att.filename || 'unnamed', contentType: att.contentType || 'application/octet-stream', size: att.size, }); } } } await client.logout(); return attachments; } catch (error) { try { await client.logout(); } catch { // Ignorieren } throw error; } } // ==================== TRASH OPERATIONS ==================== // Typische Namen für Trash-Ordner (verschiedene Server/Sprachen) const TRASH_FOLDER_NAMES = ['Trash', 'INBOX.Trash', 'Deleted', 'Deleted Items', 'Deleted Messages', 'Papierkorb']; // Helper: Trash-Ordner finden async function findTrashFolder(client: ImapFlow): Promise { const mailboxes = await client.list(); for (const name of TRASH_FOLDER_NAMES) { const found = mailboxes.find(m => m.path.toLowerCase() === name.toLowerCase() || m.name.toLowerCase() === name.toLowerCase() ); if (found) return found.path; } // Suche nach Ordner mit \Trash Flag const trashByFlag = mailboxes.find(m => m.specialUse === '\\Trash'); if (trashByFlag) return trashByFlag.path; return null; } export interface MoveToTrashResult { success: boolean; newUid?: number; // UID im Trash-Ordner error?: string; } // E-Mail in Papierkorb verschieben export async function moveToTrash( credentials: ImapCredentials, uid: number, sourceFolder: string = 'INBOX' ): Promise { const encryption = credentials.encryption ?? 'SSL'; const rejectUnauthorized = !credentials.allowSelfSignedCerts; const clientOptions: ConstructorParameters[0] = { host: credentials.host, port: credentials.port, secure: encryption === 'SSL', auth: { user: credentials.user, pass: credentials.password, }, logger: false, }; if (encryption !== 'NONE') { const __tls = buildTlsOptions(credentials); if (__tls) clientOptions.tls = __tls as any; } const client = new ImapFlow(clientOptions); try { await client.connect(); // Trash-Ordner finden const trashFolder = await findTrashFolder(client); if (!trashFolder) { await client.logout(); return { success: false, error: 'Trash-Ordner nicht gefunden' }; } // Source-Ordner öffnen await client.mailboxOpen(sourceFolder); // E-Mail verschieben (kopieren + löschen) const moveResult = await client.messageMove([uid], trashFolder, { uid: true }); await client.logout(); if (moveResult && moveResult.uidMap) { // uidMap ist Map - alte UID -> neue UID const newUid = moveResult.uidMap.get(uid); console.log(`[IMAP] Email moved to ${trashFolder}, new UID: ${newUid}`); return { success: true, newUid }; } return { success: true }; } catch (error) { try { await client.logout(); } catch { // Ignorieren } console.error('[IMAP] Error moving to trash:', error); return { success: false, error: error instanceof Error ? error.message : 'Fehler beim Verschieben in Papierkorb', }; } } export interface RestoreFromTrashResult { success: boolean; newUid?: number; // UID im wiederhergestellten Ordner error?: string; } // E-Mail aus Papierkorb wiederherstellen export async function restoreFromTrash( credentials: ImapCredentials, uid: number, targetFolder: string = 'INBOX' ): Promise { const encryption = credentials.encryption ?? 'SSL'; const rejectUnauthorized = !credentials.allowSelfSignedCerts; const clientOptions: ConstructorParameters[0] = { host: credentials.host, port: credentials.port, secure: encryption === 'SSL', auth: { user: credentials.user, pass: credentials.password, }, logger: false, }; if (encryption !== 'NONE') { const __tls = buildTlsOptions(credentials); if (__tls) clientOptions.tls = __tls as any; } const client = new ImapFlow(clientOptions); try { await client.connect(); // Trash-Ordner finden const trashFolder = await findTrashFolder(client); if (!trashFolder) { await client.logout(); return { success: false, error: 'Trash-Ordner nicht gefunden' }; } // Trash-Ordner öffnen await client.mailboxOpen(trashFolder); // E-Mail zurück verschieben const moveResult = await client.messageMove([uid], targetFolder, { uid: true }); await client.logout(); if (moveResult && moveResult.uidMap) { const newUid = moveResult.uidMap.get(uid); console.log(`[IMAP] Email restored to ${targetFolder}, new UID: ${newUid}`); return { success: true, newUid }; } return { success: true }; } catch (error) { try { await client.logout(); } catch { // Ignorieren } console.error('[IMAP] Error restoring from trash:', error); return { success: false, error: error instanceof Error ? error.message : 'Fehler beim Wiederherstellen', }; } } export interface PermanentDeleteResult { success: boolean; error?: string; } // E-Mail endgültig löschen (aus Trash-Ordner) export async function permanentDelete( credentials: ImapCredentials, uid: number, folder?: string // Optional: Ordner angeben, sonst wird Trash verwendet ): Promise { const encryption = credentials.encryption ?? 'SSL'; const rejectUnauthorized = !credentials.allowSelfSignedCerts; const clientOptions: ConstructorParameters[0] = { host: credentials.host, port: credentials.port, secure: encryption === 'SSL', auth: { user: credentials.user, pass: credentials.password, }, logger: false, }; if (encryption !== 'NONE') { const __tls = buildTlsOptions(credentials); if (__tls) clientOptions.tls = __tls as any; } const client = new ImapFlow(clientOptions); try { await client.connect(); // Ordner bestimmen let targetFolder: string | null | undefined = folder; if (!targetFolder) { targetFolder = await findTrashFolder(client); if (!targetFolder) { await client.logout(); return { success: false, error: 'Trash-Ordner nicht gefunden' }; } } // Ordner öffnen await client.mailboxOpen(targetFolder); // E-Mail als gelöscht markieren und expunge await client.messageDelete([uid], { uid: true }); await client.logout(); console.log(`[IMAP] Email permanently deleted from ${targetFolder}, UID: ${uid}`); return { success: true }; } catch (error) { try { await client.logout(); } catch { // Ignorieren } console.error('[IMAP] Error permanently deleting:', error); return { success: false, error: error instanceof Error ? error.message : 'Fehler beim endgültigen Löschen', }; } }