194c86409f
Follow-up zu a83358b/24e152b. plesk bin mail --help auf Prod zeigt: - -forwarding-addresses akzeptiert NUR add: und del:, kein set: → unser set:-Befehl wurde silent verworfen, Sync hatte nie Wirkung. - -mailgroup als Option existiert gar nicht. Plesk nutzt -forwarding als Mailgroup-Schalter (im --info als "Mailgroup:" ausgegeben, im CLI als "-forwarding" gesetzt). Mein vorheriges -mailgroup false triggerte "Unrecognized option". updateForwardTargets jetzt: 1. Aktuelle Members aus emailExists holen 2. Diff: toRemove = current \ targets, toAdd = targets \ current (case-insensitive) 3. Wenn toRemove: --update -forwarding-addresses del:<liste> 4. Wenn toAdd: --update -forwarding true -forwarding-addresses add:<liste> Idempotent, weil add/del Duplikate bzw. nicht-existente ignorieren. Smoke-Test mit Prod-Stand (3 Bestands-Members + 1 neuer Eintrag): nichts entfernt, nur bzirks@gmx.de hinzugefügt.
572 lines
20 KiB
TypeScript
572 lines
20 KiB
TypeScript
// ==================== PLESK EMAIL PROVIDER ====================
|
||
|
||
import { Agent, fetch as undiciFetch } from 'undici';
|
||
import {
|
||
IEmailProvider,
|
||
EmailProviderConfig,
|
||
EmailExistsResult,
|
||
EmailOperationResult,
|
||
CreateEmailParams,
|
||
CreateEmailWithMailboxParams,
|
||
CreateEmailWithMailboxResult,
|
||
EnableMailboxParams,
|
||
UpdateMailboxPasswordParams,
|
||
RenameEmailParams,
|
||
} from './types.js';
|
||
|
||
// Undici-Agent der selbstsignierte Zertifikate akzeptiert
|
||
// Mit Timeouts und Connection-Limits um Probleme zu vermeiden
|
||
const httpsAgent = new Agent({
|
||
connect: {
|
||
rejectUnauthorized: false,
|
||
timeout: 10000, // 10 Sekunden Connect-Timeout
|
||
},
|
||
bodyTimeout: 30000, // 30 Sekunden für Response-Body
|
||
headersTimeout: 30000, // 30 Sekunden für Headers
|
||
keepAliveTimeout: 1000, // Connections nach 1 Sekunde schließen
|
||
keepAliveMaxTimeout: 5000, // Maximal 5 Sekunden Keep-Alive
|
||
connections: 1, // Nur eine Connection gleichzeitig pro Host
|
||
pipelining: 1, // Kein Pipelining
|
||
});
|
||
|
||
export class PleskEmailProvider implements IEmailProvider {
|
||
readonly type = 'PLESK';
|
||
private config: EmailProviderConfig;
|
||
|
||
constructor(config: EmailProviderConfig) {
|
||
this.config = config;
|
||
}
|
||
|
||
// Basis-URL für API-Requests
|
||
private get baseUrl(): string {
|
||
// Entferne trailing slash falls vorhanden
|
||
return this.config.apiUrl.replace(/\/$/, '');
|
||
}
|
||
|
||
// HTTP-Request an Plesk API senden
|
||
private async request<T>(
|
||
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
|
||
endpoint: string,
|
||
data?: Record<string, unknown>
|
||
): Promise<T> {
|
||
const url = `${this.baseUrl}${endpoint}`;
|
||
|
||
const headers: Record<string, string> = {
|
||
'Content-Type': 'application/json',
|
||
Accept: 'application/json',
|
||
};
|
||
|
||
// Authentifizierung: API-Key hat Priorität, sonst Basic Auth
|
||
if (this.config.apiKey) {
|
||
// Nur API-Key verwenden (ohne Basic Auth)
|
||
headers['X-API-Key'] = this.config.apiKey;
|
||
} else if (this.config.username && this.config.password) {
|
||
// Basic Auth nur wenn kein API-Key
|
||
const authHeader = Buffer.from(
|
||
`${this.config.username}:${this.config.password}`
|
||
).toString('base64');
|
||
headers['Authorization'] = `Basic ${authHeader}`;
|
||
} else {
|
||
// Keine Authentifizierung vorhanden
|
||
throw new Error('Keine Zugangsdaten angegeben - bitte API-Key oder Benutzername/Passwort eingeben');
|
||
}
|
||
|
||
const options: Parameters<typeof undiciFetch>[1] = {
|
||
method,
|
||
headers,
|
||
dispatcher: httpsAgent,
|
||
};
|
||
|
||
if (data && (method === 'POST' || method === 'PUT')) {
|
||
options.body = JSON.stringify(data);
|
||
}
|
||
|
||
try {
|
||
const response = await undiciFetch(url, options);
|
||
|
||
if (!response.ok) {
|
||
const errorText = await response.text();
|
||
throw new Error(`Plesk API Fehler: ${response.status} - ${errorText}`);
|
||
}
|
||
|
||
// Leere Response bei DELETE
|
||
if (response.status === 204) {
|
||
return {} as T;
|
||
}
|
||
|
||
return await response.json() as T;
|
||
} catch (error) {
|
||
// Verbesserte Fehlermeldungen für häufige Probleme
|
||
if (error instanceof Error) {
|
||
const msg = error.message.toLowerCase();
|
||
|
||
// Netzwerkfehler
|
||
if (msg.includes('econnrefused')) {
|
||
throw new Error(`Server nicht erreichbar unter ${this.baseUrl} - Ist der Server gestartet?`);
|
||
}
|
||
if (msg.includes('enotfound') || msg.includes('getaddrinfo')) {
|
||
throw new Error(`Server-Adresse nicht gefunden: ${this.baseUrl} - Bitte URL prüfen`);
|
||
}
|
||
if (msg.includes('etimedout') || msg.includes('timeout')) {
|
||
throw new Error(`Zeitüberschreitung bei Verbindung zu ${this.baseUrl}`);
|
||
}
|
||
if (msg.includes('econnreset')) {
|
||
throw new Error(`Verbindung wurde vom Server abgebrochen`);
|
||
}
|
||
|
||
// SSL/TLS Fehler
|
||
if (msg.includes('cert') || msg.includes('ssl') || msg.includes('tls') || msg.includes('unable_to_verify')) {
|
||
throw new Error(`SSL-Zertifikatsfehler - Selbstsigniertes Zertifikat wird nicht akzeptiert`);
|
||
}
|
||
|
||
// fetch failed ist meist ein Netzwerk/SSL Problem
|
||
if (msg.includes('fetch failed')) {
|
||
throw new Error(`Verbindung fehlgeschlagen zu ${this.baseUrl} - Bitte prüfen: Server erreichbar? HTTPS-Port korrekt?`);
|
||
}
|
||
}
|
||
|
||
console.error('Plesk API Request failed:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
async testConnection(): Promise<void> {
|
||
// Versuche Server-Info abzurufen - wirft Fehler bei Auth-Problemen
|
||
try {
|
||
await this.request('GET', '/api/v2/server');
|
||
} catch (error) {
|
||
if (error instanceof Error) {
|
||
// Verbesserte Fehlermeldung
|
||
if (error.message.includes('401')) {
|
||
throw new Error('Authentifizierung fehlgeschlagen - Benutzername/Passwort oder API-Key prüfen');
|
||
}
|
||
if (error.message.includes('403')) {
|
||
throw new Error('Zugriff verweigert - Berechtigungen prüfen');
|
||
}
|
||
// Andere Fehler wurden schon in request() übersetzt
|
||
}
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
async emailExists(localPart: string): Promise<EmailExistsResult> {
|
||
const email = `${localPart}@${this.config.domain}`;
|
||
|
||
try {
|
||
// Plesk CLI API: Mail-Info abfragen
|
||
const result = await this.request<{ code: number; stdout: string; stderr: string }>(
|
||
'POST',
|
||
'/api/v2/cli/mail/call',
|
||
{ params: ['--info', email] }
|
||
);
|
||
|
||
// Debug: Response-Struktur loggen
|
||
console.log('Plesk emailExists response:', JSON.stringify(result, null, 2));
|
||
|
||
// Plesk gibt code=0 bei Erfolg, code!=0 bei Fehler
|
||
// stderr enthält Fehlermeldung wenn Mail nicht existiert
|
||
const hasError = result.code !== 0 ||
|
||
result.stderr?.toLowerCase().includes('not found') ||
|
||
result.stderr?.toLowerCase().includes('does not exist') ||
|
||
result.stderr?.toLowerCase().includes('unable to find') ||
|
||
result.stderr?.toLowerCase().includes('no such');
|
||
|
||
if (hasError) {
|
||
return { exists: false };
|
||
}
|
||
|
||
// stdout sollte die Mail-Infos enthalten
|
||
const exists = result.stdout?.toLowerCase().includes(localPart.toLowerCase());
|
||
|
||
// Mailbox-Status aus stdout parsen (Format: "Mailbox: true" oder "Mailbox: false")
|
||
let hasMailbox: boolean | undefined;
|
||
let mailgroupActive: boolean | undefined;
|
||
let mailgroupMembers: string[] | undefined;
|
||
let forwardingActive: boolean | undefined;
|
||
let forwardingTargets: string[] | undefined;
|
||
if (exists && result.stdout) {
|
||
const mailboxMatch = result.stdout.match(/Mailbox:\s*(true|false)/i);
|
||
if (mailboxMatch) {
|
||
hasMailbox = mailboxMatch[1].toLowerCase() === 'true';
|
||
}
|
||
|
||
// Mailgroup-Status + Mitglieder. Plesk listet sie auf einer
|
||
// Zeile, Adressen sind durch Whitespace getrennt.
|
||
const mailgroupMatch = result.stdout.match(/Mailgroup:\s*(true|false)/i);
|
||
if (mailgroupMatch) {
|
||
mailgroupActive = mailgroupMatch[1].toLowerCase() === 'true';
|
||
}
|
||
const groupMembersMatch = result.stdout.match(/Group member\(s\):\s*([^\n]*)/i);
|
||
if (groupMembersMatch) {
|
||
mailgroupMembers = groupMembersMatch[1]
|
||
.trim()
|
||
.split(/\s+/)
|
||
.filter((m) => m.includes('@'));
|
||
}
|
||
|
||
// Forwarding-Status + Ziele. Plesk druckt "Forward request: <addrs>".
|
||
// Auf manchen Plesk-Versionen heißt das Feld auch "Forwarding".
|
||
const forwardActiveMatch = result.stdout.match(/Forwarding:\s*(true|false)/i);
|
||
if (forwardActiveMatch) {
|
||
forwardingActive = forwardActiveMatch[1].toLowerCase() === 'true';
|
||
}
|
||
const forwardTargetsMatch = result.stdout.match(/Forward(?:ing)?(?: request)?:\s*([^\n]*)/i);
|
||
if (forwardTargetsMatch) {
|
||
forwardingTargets = forwardTargetsMatch[1]
|
||
.trim()
|
||
.split(/\s+/)
|
||
.filter((m) => m.includes('@'));
|
||
if (forwardingActive === undefined) {
|
||
forwardingActive = (forwardingTargets?.length ?? 0) > 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
return {
|
||
exists,
|
||
email: exists ? email : undefined,
|
||
hasMailbox,
|
||
mailgroupActive,
|
||
mailgroupMembers,
|
||
forwardingActive,
|
||
forwardingTargets,
|
||
};
|
||
} catch (error) {
|
||
// HTTP-Fehler oder Netzwerkfehler
|
||
if (error instanceof Error) {
|
||
const msg = error.message.toLowerCase();
|
||
// "not found" = Mail gibt es nicht
|
||
if (msg.includes('not found') || msg.includes('does not exist') || msg.includes('unable to find')) {
|
||
return { exists: false };
|
||
}
|
||
}
|
||
console.error('Plesk emailExists error:', error);
|
||
return { exists: false };
|
||
}
|
||
}
|
||
|
||
async createEmail(params: CreateEmailParams): Promise<EmailOperationResult> {
|
||
const { localPart, forwardTargets } = params;
|
||
const email = `${localPart}@${this.config.domain}`;
|
||
|
||
try {
|
||
// Prüfen ob schon existiert
|
||
const exists = await this.emailExists(localPart);
|
||
if (exists.exists) {
|
||
return {
|
||
success: false,
|
||
error: `E-Mail ${email} existiert bereits`,
|
||
};
|
||
}
|
||
|
||
// Plesk CLI API: Mail-Account mit Weiterleitung erstellen
|
||
// Verwendet den CLI-Wrapper unter /api/v2/cli/mail/call
|
||
// Format für -forwarding-addresses: "add:email1,email2" oder "set:email1,email2"
|
||
await this.request('POST', '/api/v2/cli/mail/call', {
|
||
params: [
|
||
'--create', email,
|
||
'-forwarding', 'true',
|
||
'-forwarding-addresses', `add:${forwardTargets.join(',')}`,
|
||
'-mailbox', 'false',
|
||
],
|
||
});
|
||
|
||
return {
|
||
success: true,
|
||
message: `E-Mail ${email} erfolgreich erstellt mit Weiterleitung an: ${forwardTargets.join(', ')}`,
|
||
};
|
||
} catch (error) {
|
||
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
|
||
console.error('Plesk createEmail error:', error);
|
||
return {
|
||
success: false,
|
||
error: `Fehler beim Erstellen der E-Mail: ${errorMessage}`,
|
||
};
|
||
}
|
||
}
|
||
|
||
async createEmailWithMailbox(params: CreateEmailWithMailboxParams): Promise<CreateEmailWithMailboxResult> {
|
||
const { localPart, forwardTargets, password } = params;
|
||
const email = `${localPart}@${this.config.domain}`;
|
||
|
||
try {
|
||
// Prüfen ob schon existiert
|
||
const exists = await this.emailExists(localPart);
|
||
if (exists.exists) {
|
||
return {
|
||
success: false,
|
||
error: `E-Mail ${email} existiert bereits`,
|
||
};
|
||
}
|
||
|
||
// Plesk CLI API: Mail-Account mit echter Mailbox erstellen
|
||
// -mailbox true: Echte Mailbox (IMAP/SMTP-Zugang)
|
||
// -passwd: Passwort für die Mailbox
|
||
// -forwarding true: Zusätzlich Weiterleitung aktivieren
|
||
await this.request('POST', '/api/v2/cli/mail/call', {
|
||
params: [
|
||
'--create', email,
|
||
'-mailbox', 'true',
|
||
'-passwd', password,
|
||
'-forwarding', 'true',
|
||
'-forwarding-addresses', `add:${forwardTargets.join(',')}`,
|
||
],
|
||
});
|
||
|
||
return {
|
||
success: true,
|
||
message: `E-Mail ${email} mit Mailbox erfolgreich erstellt`,
|
||
email,
|
||
};
|
||
} catch (error) {
|
||
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
|
||
console.error('Plesk createEmailWithMailbox error:', error);
|
||
return {
|
||
success: false,
|
||
error: `Fehler beim Erstellen der E-Mail mit Mailbox: ${errorMessage}`,
|
||
};
|
||
}
|
||
}
|
||
|
||
async enableMailboxForExisting(params: EnableMailboxParams): Promise<EmailOperationResult> {
|
||
const { localPart, password } = params;
|
||
const email = `${localPart}@${this.config.domain}`;
|
||
|
||
try {
|
||
// Prüfen ob E-Mail existiert
|
||
const exists = await this.emailExists(localPart);
|
||
if (!exists.exists) {
|
||
return {
|
||
success: false,
|
||
error: `E-Mail ${email} nicht gefunden`,
|
||
};
|
||
}
|
||
|
||
// Plesk CLI API: Mailbox für existierende E-Mail aktivieren
|
||
// --update: Existierende E-Mail aktualisieren
|
||
// -mailbox true: Mailbox aktivieren
|
||
// -passwd: Passwort für die Mailbox setzen
|
||
await this.request('POST', '/api/v2/cli/mail/call', {
|
||
params: [
|
||
'--update', email,
|
||
'-mailbox', 'true',
|
||
'-passwd', password,
|
||
],
|
||
});
|
||
|
||
return {
|
||
success: true,
|
||
message: `Mailbox für ${email} erfolgreich aktiviert`,
|
||
};
|
||
} catch (error) {
|
||
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
|
||
console.error('Plesk enableMailboxForExisting error:', error);
|
||
return {
|
||
success: false,
|
||
error: `Fehler beim Aktivieren der Mailbox: ${errorMessage}`,
|
||
};
|
||
}
|
||
}
|
||
|
||
async updateMailboxPassword(params: UpdateMailboxPasswordParams): Promise<EmailOperationResult> {
|
||
const { localPart, password } = params;
|
||
const email = `${localPart}@${this.config.domain}`;
|
||
|
||
try {
|
||
// Prüfen ob E-Mail existiert
|
||
const exists = await this.emailExists(localPart);
|
||
if (!exists.exists) {
|
||
return {
|
||
success: false,
|
||
error: `E-Mail ${email} nicht gefunden`,
|
||
};
|
||
}
|
||
|
||
// Plesk CLI API: Passwort für existierende E-Mail aktualisieren
|
||
// --update: Existierende E-Mail aktualisieren
|
||
// -passwd: Neues Passwort setzen
|
||
await this.request('POST', '/api/v2/cli/mail/call', {
|
||
params: [
|
||
'--update', email,
|
||
'-passwd', password,
|
||
],
|
||
});
|
||
|
||
return {
|
||
success: true,
|
||
message: `Passwort für ${email} erfolgreich aktualisiert`,
|
||
};
|
||
} catch (error) {
|
||
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
|
||
console.error('Plesk updateMailboxPassword error:', error);
|
||
return {
|
||
success: false,
|
||
error: `Fehler beim Aktualisieren des Passworts: ${errorMessage}`,
|
||
};
|
||
}
|
||
}
|
||
|
||
async deleteEmail(localPart: string): Promise<EmailOperationResult> {
|
||
const email = `${localPart}@${this.config.domain}`;
|
||
|
||
try {
|
||
// Prüfen ob Mail existiert
|
||
const exists = await this.emailExists(localPart);
|
||
if (!exists.exists) {
|
||
return {
|
||
success: false,
|
||
error: `E-Mail ${email} nicht gefunden`,
|
||
};
|
||
}
|
||
|
||
// Plesk CLI API: Mail-Account löschen
|
||
await this.request('POST', '/api/v2/cli/mail/call', {
|
||
params: ['--remove', email],
|
||
});
|
||
|
||
return {
|
||
success: true,
|
||
message: `E-Mail ${email} erfolgreich gelöscht`,
|
||
};
|
||
} catch (error) {
|
||
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
|
||
console.error('Plesk deleteEmail error:', error);
|
||
return {
|
||
success: false,
|
||
error: `Fehler beim Löschen der E-Mail: ${errorMessage}`,
|
||
};
|
||
}
|
||
}
|
||
|
||
async renameEmail(params: RenameEmailParams): Promise<EmailOperationResult> {
|
||
const { oldLocalPart, newLocalPart } = params;
|
||
const oldEmail = `${oldLocalPart}@${this.config.domain}`;
|
||
const newEmail = `${newLocalPart}@${this.config.domain}`;
|
||
|
||
try {
|
||
// Prüfen ob alte Mail existiert
|
||
const oldExists = await this.emailExists(oldLocalPart);
|
||
if (!oldExists.exists) {
|
||
return {
|
||
success: false,
|
||
error: `E-Mail ${oldEmail} nicht gefunden`,
|
||
};
|
||
}
|
||
|
||
// Prüfen ob neue Adresse schon existiert
|
||
const newExists = await this.emailExists(newLocalPart);
|
||
if (newExists.exists) {
|
||
return {
|
||
success: false,
|
||
error: `E-Mail ${newEmail} existiert bereits`,
|
||
};
|
||
}
|
||
|
||
// Plesk CLI API: Mail-Account umbenennen
|
||
await this.request('POST', '/api/v2/cli/mail/call', {
|
||
params: ['--rename', oldEmail, '-new-name', newLocalPart],
|
||
});
|
||
|
||
return {
|
||
success: true,
|
||
message: `E-Mail erfolgreich umbenannt von ${oldEmail} zu ${newEmail}`,
|
||
};
|
||
} catch (error) {
|
||
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
|
||
console.error('Plesk renameEmail error:', error);
|
||
return {
|
||
success: false,
|
||
error: `Fehler beim Umbenennen der E-Mail: ${errorMessage}`,
|
||
};
|
||
}
|
||
}
|
||
|
||
async updateForwardTargets(
|
||
localPart: string,
|
||
targets: string[]
|
||
): Promise<EmailOperationResult> {
|
||
const email = `${localPart}@${this.config.domain}`;
|
||
|
||
try {
|
||
// Prüfen ob Mail existiert
|
||
const exists = await this.emailExists(localPart);
|
||
if (!exists.exists) {
|
||
return {
|
||
success: false,
|
||
error: `E-Mail ${email} nicht gefunden`,
|
||
};
|
||
}
|
||
|
||
// Plesk-CLI-Eigenheit: `-forwarding-addresses` akzeptiert NUR
|
||
// `add:` und `del:`, KEIN `set:`. Und `-forwarding` ist der
|
||
// Mailgroup-Schalter (Plesk nennt das im `--info` "Mailgroup",
|
||
// im CLI "forwarding" – derselbe Mechanismus, doppelt benannt).
|
||
// Es gibt KEINE separaten Mailgroup-Optionen wie `-mailgroup`.
|
||
//
|
||
// Wir bauen daher den Diff: alte Member abrufen, dann
|
||
// del:<entfernt> + add:<neu> in zwei separaten Calls. Idempotent,
|
||
// weil add: Duplikate ignoriert und del: nicht-vorhandene auch.
|
||
const currentMembers = exists.mailgroupMembers ?? [];
|
||
const targetsLower = new Set(targets.map((t) => t.toLowerCase()));
|
||
const currentLower = new Set(currentMembers.map((m) => m.toLowerCase()));
|
||
const toRemove = currentMembers.filter((m) => !targetsLower.has(m.toLowerCase()));
|
||
const toAdd = targets.filter((t) => !currentLower.has(t.toLowerCase()));
|
||
|
||
console.log(
|
||
`[Plesk updateForwardTargets] ${email} – aktuell: [${currentMembers.join(', ')}], ` +
|
||
`soll: [${targets.join(', ')}], entfernen: [${toRemove.join(', ')}], hinzufügen: [${toAdd.join(', ')}]`,
|
||
);
|
||
|
||
// Entfernen-Schritt
|
||
if (toRemove.length > 0) {
|
||
const delParams = [
|
||
'--update', email,
|
||
'-forwarding-addresses', `del:${toRemove.join(',')}`,
|
||
];
|
||
const delResult = await this.request<{ code: number; stdout: string; stderr: string }>(
|
||
'POST', '/api/v2/cli/mail/call', { params: delParams },
|
||
);
|
||
console.log('[Plesk updateForwardTargets] del response:', JSON.stringify(delResult, null, 2));
|
||
if (delResult.code !== 0 || /error|failed/i.test(delResult.stderr || '')) {
|
||
return {
|
||
success: false,
|
||
error: delResult.stderr?.trim() || delResult.stdout?.trim() || `Plesk del returned code ${delResult.code}`,
|
||
};
|
||
}
|
||
}
|
||
|
||
// Hinzufügen-Schritt (impliziert -forwarding true, damit Mailgroup
|
||
// aktiviert bleibt bzw. wird).
|
||
if (toAdd.length > 0) {
|
||
const addParams = [
|
||
'--update', email,
|
||
'-forwarding', 'true',
|
||
'-forwarding-addresses', `add:${toAdd.join(',')}`,
|
||
];
|
||
const addResult = await this.request<{ code: number; stdout: string; stderr: string }>(
|
||
'POST', '/api/v2/cli/mail/call', { params: addParams },
|
||
);
|
||
console.log('[Plesk updateForwardTargets] add response:', JSON.stringify(addResult, null, 2));
|
||
if (addResult.code !== 0 || /error|failed/i.test(addResult.stderr || '')) {
|
||
return {
|
||
success: false,
|
||
error: addResult.stderr?.trim() || addResult.stdout?.trim() || `Plesk add returned code ${addResult.code}`,
|
||
};
|
||
}
|
||
}
|
||
|
||
return {
|
||
success: true,
|
||
message: `Weiterleitungen für ${email} aktualisiert: ${targets.join(', ')}`,
|
||
};
|
||
} catch (error) {
|
||
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
|
||
console.error('Plesk updateForwardTargets error:', error);
|
||
return {
|
||
success: false,
|
||
error: `Fehler beim Aktualisieren der Weiterleitungen: ${errorMessage}`,
|
||
};
|
||
}
|
||
}
|
||
}
|