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:
@@ -80,6 +80,13 @@ export default function EmailProviders() {
|
||||
// Test-Status pro Provider in der Liste
|
||||
const [providerTestResults, setProviderTestResults] = useState<Record<number, TestResult | null>>({});
|
||||
const [testingProviderId, setTestingProviderId] = useState<number | null>(null);
|
||||
// E-Mail-Zugang-Test
|
||||
const [isTestingMailAccess, setIsTestingMailAccess] = useState(false);
|
||||
const [mailAccessResult, setMailAccessResult] = useState<{
|
||||
imap: { success: boolean; error?: string; server: string; port: number; encryption: string };
|
||||
smtp: { success: boolean; error?: string; server: string; port: number; encryption: string };
|
||||
user: string;
|
||||
} | null>(null);
|
||||
|
||||
const { data: configsData, isLoading } = useQuery({
|
||||
queryKey: ['email-provider-configs'],
|
||||
@@ -151,6 +158,7 @@ export default function EmailProviders() {
|
||||
setFormData(emptyForm);
|
||||
setShowPassword(false);
|
||||
setModalTestResult(null);
|
||||
setMailAccessResult(null);
|
||||
};
|
||||
|
||||
// Test für einen gespeicherten Provider in der Liste
|
||||
@@ -216,6 +224,57 @@ export default function EmailProviders() {
|
||||
}
|
||||
};
|
||||
|
||||
// IMAP + SMTP-Zugang der System-E-Mail testen
|
||||
const handleTestMailAccess = async () => {
|
||||
if (!formData.systemEmailAddress) {
|
||||
setMailAccessResult({
|
||||
imap: { success: false, error: 'System-E-Mail-Adresse fehlt', server: '', port: 0, encryption: '' },
|
||||
smtp: { success: false, error: 'System-E-Mail-Adresse fehlt', server: '', port: 0, encryption: '' },
|
||||
user: '',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setIsTestingMailAccess(true);
|
||||
setMailAccessResult(null);
|
||||
try {
|
||||
const body: Parameters<typeof emailProviderApi.testMailAccess>[0] = editingId
|
||||
? {
|
||||
id: editingId,
|
||||
systemEmailAddress: formData.systemEmailAddress,
|
||||
systemEmailPassword: formData.systemEmailPassword || undefined,
|
||||
}
|
||||
: {
|
||||
apiUrl: formData.apiUrl,
|
||||
domain: formData.domain,
|
||||
systemEmailAddress: formData.systemEmailAddress,
|
||||
systemEmailPassword: formData.systemEmailPassword,
|
||||
imapEncryption: formData.imapEncryption,
|
||||
smtpEncryption: formData.smtpEncryption,
|
||||
allowSelfSignedCerts: formData.allowSelfSignedCerts,
|
||||
};
|
||||
|
||||
const result = await emailProviderApi.testMailAccess(body);
|
||||
if (result.data) {
|
||||
setMailAccessResult(result.data);
|
||||
}
|
||||
} catch (error) {
|
||||
setMailAccessResult({
|
||||
imap: {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Test',
|
||||
server: '',
|
||||
port: 0,
|
||||
encryption: '',
|
||||
},
|
||||
smtp: { success: false, server: '', port: 0, encryption: '' },
|
||||
user: '',
|
||||
});
|
||||
} finally {
|
||||
setIsTestingMailAccess(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -659,6 +718,83 @@ export default function EmailProviders() {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* E-Mail-Zugang testen (IMAP + SMTP) */}
|
||||
<div className="mt-4">
|
||||
<p className="text-xs text-gray-500 mb-2">
|
||||
Testet den tatsächlichen E-Mail-Zugang (IMAP-Empfang und SMTP-Versand) der System-E-Mail.
|
||||
</p>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={handleTestMailAccess}
|
||||
disabled={isTestingMailAccess || !formData.systemEmailAddress}
|
||||
className="w-full"
|
||||
>
|
||||
{isTestingMailAccess ? (
|
||||
'Teste IMAP + SMTP...'
|
||||
) : (
|
||||
<>
|
||||
<Mail className="w-4 h-4 mr-2" />
|
||||
E-Mail-Zugang testen (IMAP + SMTP)
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{mailAccessResult && (
|
||||
<div className="mt-2 space-y-2">
|
||||
{/* IMAP-Ergebnis */}
|
||||
<div className={`p-3 rounded-lg text-sm ${mailAccessResult.imap.success ? 'bg-green-50 text-green-800' : 'bg-red-50 text-red-800'}`}>
|
||||
{mailAccessResult.imap.success ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<Check className="w-4 h-4 flex-shrink-0" />
|
||||
<span>
|
||||
<strong>IMAP</strong> erfolgreich ({mailAccessResult.imap.server}:{mailAccessResult.imap.port}, {mailAccessResult.imap.encryption})
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-start gap-2">
|
||||
<WifiOff className="w-4 h-4 flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<strong>IMAP</strong> fehlgeschlagen
|
||||
{mailAccessResult.imap.server && (
|
||||
<span className="text-xs opacity-75"> ({mailAccessResult.imap.server}:{mailAccessResult.imap.port})</span>
|
||||
)}
|
||||
{mailAccessResult.imap.error && (
|
||||
<div className="mt-1 text-xs">{mailAccessResult.imap.error}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* SMTP-Ergebnis */}
|
||||
<div className={`p-3 rounded-lg text-sm ${mailAccessResult.smtp.success ? 'bg-green-50 text-green-800' : 'bg-red-50 text-red-800'}`}>
|
||||
{mailAccessResult.smtp.success ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<Check className="w-4 h-4 flex-shrink-0" />
|
||||
<span>
|
||||
<strong>SMTP</strong> erfolgreich ({mailAccessResult.smtp.server}:{mailAccessResult.smtp.port}, {mailAccessResult.smtp.encryption})
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-start gap-2">
|
||||
<WifiOff className="w-4 h-4 flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<strong>SMTP</strong> fehlgeschlagen
|
||||
{mailAccessResult.smtp.server && (
|
||||
<span className="text-xs opacity-75"> ({mailAccessResult.smtp.server}:{mailAccessResult.smtp.port})</span>
|
||||
)}
|
||||
{mailAccessResult.smtp.error && (
|
||||
<div className="mt-1 text-xs">{mailAccessResult.smtp.error}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-3 pt-4 border-t">
|
||||
|
||||
@@ -1254,6 +1254,23 @@ export const emailProviderApi = {
|
||||
const res = await api.post<ApiResponse<EmailOperationResult>>('/email-providers/test-connection', body);
|
||||
return res.data;
|
||||
},
|
||||
testMailAccess: async (body: {
|
||||
id?: number;
|
||||
apiUrl?: string;
|
||||
domain?: string;
|
||||
systemEmailAddress?: string;
|
||||
systemEmailPassword?: string;
|
||||
imapEncryption?: 'SSL' | 'STARTTLS' | 'NONE';
|
||||
smtpEncryption?: 'SSL' | 'STARTTLS' | 'NONE';
|
||||
allowSelfSignedCerts?: boolean;
|
||||
}) => {
|
||||
const res = await api.post<ApiResponse<{
|
||||
imap: { success: boolean; error?: string; server: string; port: number; encryption: string };
|
||||
smtp: { success: boolean; error?: string; server: string; port: number; encryption: string };
|
||||
user: string;
|
||||
}>>('/email-providers/test-mail-access', body);
|
||||
return res.data;
|
||||
},
|
||||
getDomain: async () => {
|
||||
const res = await api.get<ApiResponse<{ domain: string | null }>>('/email-providers/domain');
|
||||
return res.data;
|
||||
|
||||
Reference in New Issue
Block a user