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:
parent
109f774d62
commit
fd55f3129f
|
|
@ -508,9 +508,24 @@ export async function downloadAttachment(req: Request, res: Response): Promise<v
|
|||
res.send(attachment.content);
|
||||
} catch (error) {
|
||||
console.error('downloadAttachment error:', error);
|
||||
const rawMsg = error instanceof Error ? error.message : 'Unbekannter Fehler';
|
||||
const lower = rawMsg.toLowerCase();
|
||||
|
||||
let friendly = rawMsg;
|
||||
if (lower.includes('socket disconnected') && lower.includes('tls')) {
|
||||
friendly =
|
||||
'IMAP-Server hat die TLS-Verbindung abgelehnt. Mögliche Ursache: selbstsigniertes Zertifikat. Bitte in den E-Mail-Provider-Einstellungen "Selbstsignierte Zertifikate erlauben" aktivieren.';
|
||||
} else if (lower.includes('econnrefused')) {
|
||||
friendly = 'IMAP-Server ist nicht erreichbar (Verbindung verweigert). Bitte Server/Port prüfen.';
|
||||
} else if (lower.includes('etimedout')) {
|
||||
friendly = 'Zeitüberschreitung beim Verbinden zum IMAP-Server. Bitte später erneut versuchen.';
|
||||
} else if (lower.includes('authentication') || lower.includes('auth')) {
|
||||
friendly = 'IMAP-Authentifizierung fehlgeschlagen. Bitte Zugangsdaten prüfen.';
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Fehler beim Herunterladen des Anhangs',
|
||||
error: `Fehler beim Herunterladen des Anhangs: ${friendly}`,
|
||||
} as ApiResponse);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,12 @@ import { Request, Response } from 'express';
|
|||
import * as emailProviderService from '../services/emailProvider/emailProviderService.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { ApiResponse } from '../types/index.js';
|
||||
import { testImapConnection, ImapCredentials } from '../services/imapService.js';
|
||||
import { testSmtpConnection, SmtpCredentials } from '../services/smtpService.js';
|
||||
import { decrypt } from '../utils/encryption.js';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// ==================== CONFIG CRUD ====================
|
||||
|
||||
|
|
@ -122,6 +128,156 @@ export async function testConnection(req: Request, res: Response): Promise<void>
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Testet IMAP + SMTP-Zugang für die System-E-Mail eines Providers.
|
||||
* - Option A: Provider-ID + optional überschreibendes Passwort aus Body (Modal)
|
||||
* - Option B: Testdaten komplett aus Body (beim Anlegen, noch nicht gespeichert)
|
||||
*/
|
||||
export async function testMailAccess(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const id = req.body?.id ? parseInt(req.body.id) : undefined;
|
||||
const bodyEmail = typeof req.body?.systemEmailAddress === 'string' ? req.body.systemEmailAddress : undefined;
|
||||
const bodyPassword = typeof req.body?.systemEmailPassword === 'string' ? req.body.systemEmailPassword : undefined;
|
||||
|
||||
let emailAddress: string | undefined;
|
||||
let password: string | undefined;
|
||||
let smtpServer: string;
|
||||
let smtpPort: number;
|
||||
let imapServer: string;
|
||||
let imapPort: number;
|
||||
let smtpEncryption: 'SSL' | 'STARTTLS' | 'NONE';
|
||||
let imapEncryption: 'SSL' | 'STARTTLS' | 'NONE';
|
||||
let allowSelfSignedCerts: boolean;
|
||||
|
||||
if (id) {
|
||||
// Gespeicherten Provider laden
|
||||
const config = await prisma.emailProviderConfig.findUnique({ where: { id } });
|
||||
if (!config) {
|
||||
res.status(404).json({ success: false, error: 'Provider nicht gefunden' } as ApiResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
emailAddress = bodyEmail || config.systemEmailAddress || undefined;
|
||||
if (bodyPassword) {
|
||||
password = bodyPassword;
|
||||
} else if (config.systemEmailPasswordEncrypted) {
|
||||
try {
|
||||
password = decrypt(config.systemEmailPasswordEncrypted);
|
||||
} catch {
|
||||
password = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// IMAP/SMTP-Settings vom Provider ableiten
|
||||
const settings = await emailProviderService.getImapSmtpSettings();
|
||||
if (!settings) {
|
||||
res.status(400).json({ success: false, error: 'Keine IMAP/SMTP-Einstellungen verfügbar' } as ApiResponse);
|
||||
return;
|
||||
}
|
||||
smtpServer = settings.smtpServer;
|
||||
smtpPort = settings.smtpPort;
|
||||
imapServer = settings.imapServer;
|
||||
imapPort = settings.imapPort;
|
||||
smtpEncryption = settings.smtpEncryption;
|
||||
imapEncryption = settings.imapEncryption;
|
||||
allowSelfSignedCerts = settings.allowSelfSignedCerts;
|
||||
} else if (req.body?.apiUrl) {
|
||||
// Formulardaten ohne gespeicherten Provider
|
||||
emailAddress = bodyEmail;
|
||||
password = bodyPassword;
|
||||
|
||||
try {
|
||||
const url = new URL(req.body.apiUrl);
|
||||
smtpServer = url.hostname;
|
||||
imapServer = url.hostname;
|
||||
} catch {
|
||||
smtpServer = `mail.${req.body.domain || ''}`;
|
||||
imapServer = smtpServer;
|
||||
}
|
||||
|
||||
imapEncryption = (req.body.imapEncryption || 'SSL') as 'SSL' | 'STARTTLS' | 'NONE';
|
||||
smtpEncryption = (req.body.smtpEncryption || 'SSL') as 'SSL' | 'STARTTLS' | 'NONE';
|
||||
allowSelfSignedCerts = !!req.body.allowSelfSignedCerts;
|
||||
|
||||
imapPort = imapEncryption === 'SSL' ? 993 : 143;
|
||||
smtpPort = smtpEncryption === 'SSL' ? 465 : smtpEncryption === 'STARTTLS' ? 587 : 25;
|
||||
} else {
|
||||
res.status(400).json({ success: false, error: 'Provider-ID oder Testdaten erforderlich' } as ApiResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!emailAddress || !password) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: 'System-E-Mail-Adresse und Passwort sind erforderlich',
|
||||
} as ApiResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
// IMAP testen
|
||||
const imapCredentials: ImapCredentials = {
|
||||
host: imapServer,
|
||||
port: imapPort,
|
||||
user: emailAddress,
|
||||
password,
|
||||
encryption: imapEncryption,
|
||||
allowSelfSignedCerts,
|
||||
};
|
||||
|
||||
// SMTP testen
|
||||
const smtpCredentials: SmtpCredentials = {
|
||||
host: smtpServer,
|
||||
port: smtpPort,
|
||||
user: emailAddress,
|
||||
password,
|
||||
encryption: smtpEncryption,
|
||||
allowSelfSignedCerts,
|
||||
};
|
||||
|
||||
let imapResult: { success: boolean; error?: string } = { success: false };
|
||||
let smtpResult: { success: boolean; error?: string } = { success: false };
|
||||
|
||||
try {
|
||||
await testImapConnection(imapCredentials);
|
||||
imapResult = { success: true };
|
||||
} catch (e) {
|
||||
imapResult = { success: false, error: e instanceof Error ? e.message : 'Unbekannter Fehler' };
|
||||
}
|
||||
|
||||
try {
|
||||
await testSmtpConnection(smtpCredentials);
|
||||
smtpResult = { success: true };
|
||||
} catch (e) {
|
||||
smtpResult = { success: false, error: e instanceof Error ? e.message : 'Unbekannter Fehler' };
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: imapResult.success && smtpResult.success,
|
||||
data: {
|
||||
imap: {
|
||||
...imapResult,
|
||||
server: imapServer,
|
||||
port: imapPort,
|
||||
encryption: imapEncryption,
|
||||
},
|
||||
smtp: {
|
||||
...smtpResult,
|
||||
server: smtpServer,
|
||||
port: smtpPort,
|
||||
encryption: smtpEncryption,
|
||||
},
|
||||
user: emailAddress,
|
||||
},
|
||||
} as ApiResponse);
|
||||
} catch (error) {
|
||||
console.error('testMailAccess error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Test',
|
||||
} as ApiResponse);
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkEmailExists(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const { localPart } = req.params;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ router.delete('/configs/:id', authenticate, requirePermission('settings:update')
|
|||
|
||||
// Email Operations
|
||||
router.post('/test-connection', authenticate, requirePermission('settings:update'), emailProviderController.testConnection);
|
||||
router.post('/test-mail-access', authenticate, requirePermission('settings:update'), emailProviderController.testMailAccess);
|
||||
router.get('/domain', authenticate, emailProviderController.getProviderDomain);
|
||||
router.get('/check/:localPart', authenticate, requirePermission('customers:read'), emailProviderController.checkEmailExists);
|
||||
router.post('/provision', authenticate, requirePermission('customers:update'), emailProviderController.provisionEmail);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue