E-Mail-Zugang Test (IMAP + SMTP) in Provider-Einstellungen
Das bestehende „Verbindung testen" prüft nur den API-Zugang (Plesk/cPanel), nicht den eigentlichen IMAP/SMTP-Zugang der System-E-Mail. Das führte dazu, dass Anhang-Downloads scheiterten obwohl der API-Test grün war. Neuer Button im EmailProviders-Modal: „E-Mail-Zugang testen (IMAP + SMTP)" - Testet IMAP-Empfang und SMTP-Versand separat - Zeigt pro Protokoll Erfolg oder Fehlermeldung mit Server/Port/Verschlüsselung - Nutzt die hinterlegte System-E-Mail-Adresse + Passwort - Funktioniert auch vor dem ersten Speichern (mit Formulardaten) Außerdem im Anhang-Download: - Retry-Mechanismus bei transienten TLS/Netzwerk-Fehlern (3 Versuche) - Socket-Timeout 30s gegen hängende Verbindungen - Sprechende Fehlermeldungen (z.B. Hinweis auf selbstsigniertes Zertifikat) - Debug-Logging mit Host/Port/User/Folder/UID Backend: - Neuer Endpoint POST /api/email-providers/test-mail-access - fetchAttachment in imapService: Retry-Wrapper + fetchAttachmentInner - Besseres Error-Handling in downloadAttachment (Cert-Hinweis, Auth, Timeout) Frontend: - emailProviderApi.testMailAccess() - EmailProviders-Modal: neuer Button + zweispaltige Ergebnis-Anzeige für IMAP+SMTP Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -360,6 +360,41 @@ export async function fetchAttachment(
|
||||
uid: number,
|
||||
attachmentFilename: string,
|
||||
folder: string = 'INBOX'
|
||||
): Promise<EmailAttachmentData | null> {
|
||||
// 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<EmailAttachmentData | null> {
|
||||
// Verschlüsselungs-Einstellungen basierend auf Modus
|
||||
const encryption = credentials.encryption ?? 'SSL';
|
||||
@@ -374,6 +409,8 @@ export async function fetchAttachment(
|
||||
pass: credentials.password,
|
||||
},
|
||||
logger: false,
|
||||
// Timeouts gegen hängende Verbindungen
|
||||
socketTimeout: 30000,
|
||||
};
|
||||
|
||||
if (encryption !== 'NONE') {
|
||||
@@ -382,37 +419,68 @@ export async function fetchAttachment(
|
||||
|
||||
const client = new ImapFlow(clientOptions);
|
||||
|
||||
console.log(`[fetchAttachment] Host: ${credentials.host}:${credentials.port} | User: ${credentials.user} | Folder: ${folder} | UID: ${uid} | File: ${attachmentFilename}`);
|
||||
|
||||
try {
|
||||
await client.connect();
|
||||
await client.mailboxOpen(folder);
|
||||
|
||||
// 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
|
||||
for await (const message of client.fetch(uid.toString(), {
|
||||
source: true,
|
||||
}, { uid: true })) {
|
||||
if (!message.source) continue;
|
||||
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);
|
||||
// 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;
|
||||
// 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();
|
||||
|
||||
Reference in New Issue
Block a user