Compare commits
3 Commits
083913cadb
...
51eb12b414
| Author | SHA1 | Date | |
|---|---|---|---|
| 51eb12b414 | |||
| c2ebc7cf1e | |||
| b4be3cebfb |
@@ -103,6 +103,45 @@ export async function deleteEmail(req: Request, res: Response): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function syncForwarding(req: AuthRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const emailId = parseInt(req.params.id);
|
||||
if (!(await canAccessStressfreiEmail(req, res, emailId))) return;
|
||||
|
||||
const result = await stressfreiEmailService.syncForwardingForEmail(emailId);
|
||||
if (!result.success) {
|
||||
res.status(400).json({ success: false, error: result.error } as ApiResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
const labelParts = [`Weiterleitungen: ${(result.forwardTargets || []).join(', ')}`];
|
||||
if (result.passwordReset) labelParts.push('Mailbox-Passwort am Provider neu gesetzt');
|
||||
|
||||
await logChange({
|
||||
req,
|
||||
action: 'UPDATE',
|
||||
resourceType: 'StressfreiEmail',
|
||||
resourceId: emailId.toString(),
|
||||
label: `Stressfrei-Sync: ${labelParts.join(' | ')}`,
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
forwardTargets: result.forwardTargets,
|
||||
customerEmail: result.customerEmail,
|
||||
passwordReset: result.passwordReset,
|
||||
},
|
||||
message: 'Weiterleitungen aktualisiert',
|
||||
} as ApiResponse);
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Synchronisieren der Weiterleitungen',
|
||||
} as ApiResponse);
|
||||
}
|
||||
}
|
||||
|
||||
export async function resetPassword(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const result = await stressfreiEmailService.resetMailboxPassword(parseInt(req.params.id));
|
||||
|
||||
@@ -12,4 +12,7 @@ router.delete('/:id', authenticate, requirePermission('customers:delete'), stres
|
||||
// Passwort zurücksetzen (generiert neues Passwort und setzt es beim Provider)
|
||||
router.post('/:id/reset-password', authenticate, requirePermission('customers:update'), stressfreiEmailController.resetPassword);
|
||||
|
||||
// Weiterleitungen neu setzen (z.B. nach Änderung der Kunden-Stamm-E-Mail)
|
||||
router.post('/:id/sync-forwarding', authenticate, requirePermission('customers:update'), stressfreiEmailController.syncForwarding);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -469,6 +469,22 @@ export async function deprovisionEmail(localPart: string): Promise<EmailOperatio
|
||||
}
|
||||
}
|
||||
|
||||
// Weiterleitungsziele ersetzen (set:, nicht add:) – nutzen wir, um nach einer
|
||||
// Kunden-Email-Änderung die Forwards einer Stressfrei-Adresse auf den neuen
|
||||
// Kunden-Inbox + unsere Service-Adresse zu setzen.
|
||||
export async function setEmailForwardTargets(
|
||||
localPart: string,
|
||||
targets: string[],
|
||||
): Promise<EmailOperationResult> {
|
||||
try {
|
||||
const provider = await getProviderInstance();
|
||||
return provider.updateForwardTargets(localPart, targets);
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
}
|
||||
|
||||
// E-Mail umbenennen
|
||||
export async function renameProvisionedEmail(
|
||||
oldLocalPart: string,
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
checkEmailExists,
|
||||
getProviderDomain,
|
||||
updateMailboxPassword,
|
||||
setEmailForwardTargets,
|
||||
getActiveProviderConfig,
|
||||
} from './emailProvider/emailProviderService.js';
|
||||
import { generateSecurePassword } from '../utils/passwordGenerator.js';
|
||||
|
||||
@@ -113,6 +115,8 @@ export async function createEmail(data: CreateEmailData) {
|
||||
...emailData,
|
||||
isActive: true,
|
||||
hasMailbox: true,
|
||||
isProvisioned: true,
|
||||
provisionedAt: new Date(),
|
||||
emailPasswordEncrypted: passwordEncrypted,
|
||||
},
|
||||
});
|
||||
@@ -131,6 +135,11 @@ export async function createEmail(data: CreateEmailData) {
|
||||
...emailData,
|
||||
isActive: true,
|
||||
hasMailbox: createMailbox || false,
|
||||
// Provisioned-Flag nur setzen wenn Provider-Aufruf gerade lief (oder
|
||||
// die Mail bei Plesk schon existierte und der „existiert bereits"-Pfad
|
||||
// gegriffen hat).
|
||||
isProvisioned: !!provisionAtProvider,
|
||||
provisionedAt: provisionAtProvider ? new Date() : null,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -201,7 +210,7 @@ export async function syncMailboxStatus(id: number): Promise<{
|
||||
}> {
|
||||
const stressfreiEmail = await prisma.stressfreiEmail.findUnique({
|
||||
where: { id },
|
||||
select: { email: true, hasMailbox: true },
|
||||
select: { email: true, hasMailbox: true, isProvisioned: true, provisionedAt: true },
|
||||
});
|
||||
|
||||
if (!stressfreiEmail) {
|
||||
@@ -213,19 +222,42 @@ export async function syncMailboxStatus(id: number): Promise<{
|
||||
// Provider-Status prüfen
|
||||
const providerStatus = await checkEmailExists(localPart);
|
||||
|
||||
// Self-Healing für `isProvisioned`: das Flag wurde in einer früheren Code-
|
||||
// Version beim Provisioning nie gesetzt → DB ist stellenweise inkonsistent
|
||||
// zum Provider. Wir reconciliieren bei jedem Status-Sync mit.
|
||||
const updates: Record<string, unknown> = {};
|
||||
|
||||
if (!providerStatus.exists) {
|
||||
// Beim Provider nicht (mehr) vorhanden → DB-Flag entsprechend
|
||||
if (stressfreiEmail.isProvisioned) {
|
||||
updates.isProvisioned = false;
|
||||
}
|
||||
if (stressfreiEmail.hasMailbox) {
|
||||
updates.hasMailbox = false;
|
||||
}
|
||||
if (Object.keys(updates).length > 0) {
|
||||
await prisma.stressfreiEmail.update({ where: { id }, data: updates });
|
||||
return { success: true, hasMailbox: false, wasUpdated: true };
|
||||
}
|
||||
return { success: true, hasMailbox: false, wasUpdated: false };
|
||||
}
|
||||
|
||||
const providerHasMailbox = providerStatus.hasMailbox === true;
|
||||
// Beim Provider vorhanden → isProvisioned auf true ziehen falls noch nicht
|
||||
if (!stressfreiEmail.isProvisioned) {
|
||||
updates.isProvisioned = true;
|
||||
if (!stressfreiEmail.provisionedAt) {
|
||||
updates.provisionedAt = new Date();
|
||||
}
|
||||
}
|
||||
|
||||
// DB aktualisieren wenn Status abweicht
|
||||
const providerHasMailbox = providerStatus.hasMailbox === true;
|
||||
if (stressfreiEmail.hasMailbox !== providerHasMailbox) {
|
||||
await prisma.stressfreiEmail.update({
|
||||
where: { id },
|
||||
data: { hasMailbox: providerHasMailbox },
|
||||
});
|
||||
console.log(`Mailbox-Status für ${stressfreiEmail.email} aktualisiert: ${stressfreiEmail.hasMailbox} -> ${providerHasMailbox}`);
|
||||
updates.hasMailbox = providerHasMailbox;
|
||||
}
|
||||
|
||||
if (Object.keys(updates).length > 0) {
|
||||
await prisma.stressfreiEmail.update({ where: { id }, data: updates });
|
||||
console.log(`Stressfrei-Status für ${stressfreiEmail.email} reconciled:`, updates);
|
||||
return { success: true, hasMailbox: providerHasMailbox, wasUpdated: true };
|
||||
}
|
||||
|
||||
@@ -251,6 +283,120 @@ export async function getDecryptedPassword(id: number): Promise<string | null> {
|
||||
}
|
||||
}
|
||||
|
||||
// Weiterleitungen einer Stressfrei-Adresse neu setzen (z.B. nach Änderung der
|
||||
// Stamm-E-Mail des Kunden). Ersetzt alle bestehenden Forwards durch
|
||||
// [aktuelle Kunden-E-Mail, defaultForwardEmail aus Provider-Config].
|
||||
//
|
||||
// Wenn die Adresse `hasMailbox` ist: setzt zusätzlich das im CRM verschlüsselt
|
||||
// hinterlegte Passwort am Provider neu (Use-Case: Plesk-Restore, manueller
|
||||
// Eingriff im Plesk-UI etc. – CRM und Provider können sich entkoppeln, sodass
|
||||
// IMAP/SMTP-Logins im CRM nicht mehr passen). Self-Healing.
|
||||
//
|
||||
// Idempotent: das Plesk-CLI `set:` überschreibt die Adressliste komplett, kein
|
||||
// Duplikat-Risiko bei Mehrfachaufruf. Wenn die Operation erfolgreich war wird
|
||||
// das `isProvisioned`-Flag automatisch auf `true` gezogen (historische
|
||||
// Einträge, bei denen das Flag nie gesetzt wurde, werden so geheilt).
|
||||
export async function syncForwardingForEmail(
|
||||
id: number,
|
||||
): Promise<{
|
||||
success: boolean;
|
||||
forwardTargets?: string[];
|
||||
customerEmail?: string;
|
||||
passwordReset?: boolean;
|
||||
error?: string;
|
||||
}> {
|
||||
const stressfreiEmail = await prisma.stressfreiEmail.findUnique({
|
||||
where: { id },
|
||||
select: {
|
||||
email: true,
|
||||
customerId: true,
|
||||
isProvisioned: true,
|
||||
hasMailbox: true,
|
||||
emailPasswordEncrypted: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!stressfreiEmail) {
|
||||
return { success: false, error: 'StressfreiEmail nicht gefunden' };
|
||||
}
|
||||
|
||||
const customer = await prisma.customer.findUnique({
|
||||
where: { id: stressfreiEmail.customerId },
|
||||
select: { email: true },
|
||||
});
|
||||
if (!customer?.email) {
|
||||
return { success: false, error: 'Kunde hat keine Stamm-E-Mail-Adresse hinterlegt' };
|
||||
}
|
||||
|
||||
const config = await getActiveProviderConfig();
|
||||
const forwardTargets: string[] = [customer.email];
|
||||
if (config?.defaultForwardEmail) {
|
||||
forwardTargets.push(config.defaultForwardEmail);
|
||||
}
|
||||
|
||||
const localPart = stressfreiEmail.email.split('@')[0];
|
||||
|
||||
// 1) Forwards neu setzen.
|
||||
const forwardResult = await setEmailForwardTargets(localPart, forwardTargets);
|
||||
if (!forwardResult.success) {
|
||||
// Wenn Plesk meldet „nicht gefunden", liefern wir eine sprechende Meldung
|
||||
// statt der rohen Provider-Nachricht.
|
||||
const err = forwardResult.error || 'Provider-Update fehlgeschlagen';
|
||||
const friendly = /not\s*found|nicht\s*gefunden/i.test(err)
|
||||
? 'E-Mail-Adresse beim Provider nicht gefunden – wurde sie dort gelöscht?'
|
||||
: err;
|
||||
return { success: false, error: friendly };
|
||||
}
|
||||
|
||||
// 2) Wenn Mailbox: Passwort aus CRM-Speicher entschlüsseln und am Provider
|
||||
// neu setzen (Self-Healing nach Provider-seitigen Änderungen).
|
||||
let passwordReset = false;
|
||||
if (stressfreiEmail.hasMailbox && stressfreiEmail.emailPasswordEncrypted) {
|
||||
try {
|
||||
const password = decrypt(stressfreiEmail.emailPasswordEncrypted);
|
||||
const pwResult = await updateMailboxPassword(localPart, password);
|
||||
if (!pwResult.success) {
|
||||
// Forwards waren schon erfolgreich – wir geben Forward-Erfolg + Passwort-
|
||||
// Fehler kombiniert zurück, statt die ganze Operation rot zu machen.
|
||||
return {
|
||||
success: false,
|
||||
forwardTargets,
|
||||
customerEmail: customer.email,
|
||||
error:
|
||||
'Weiterleitungen aktualisiert, aber Passwort-Sync fehlgeschlagen: ' +
|
||||
(pwResult.error || 'unbekannt'),
|
||||
};
|
||||
}
|
||||
passwordReset = true;
|
||||
} catch (e) {
|
||||
return {
|
||||
success: false,
|
||||
forwardTargets,
|
||||
customerEmail: customer.email,
|
||||
error:
|
||||
'Weiterleitungen aktualisiert, aber Passwort konnte nicht entschlüsselt werden – ' +
|
||||
'evtl. wurde der ENCRYPTION_KEY rotiert',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 3) Self-Healing: nach erfolgreichem Provider-Aufruf wissen wir definitiv,
|
||||
// dass die Adresse beim Provider existiert → Flag korrigieren.
|
||||
if (!stressfreiEmail.isProvisioned) {
|
||||
await prisma.stressfreiEmail.update({
|
||||
where: { id },
|
||||
data: { isProvisioned: true, provisionedAt: new Date() },
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
forwardTargets,
|
||||
customerEmail: customer.email,
|
||||
passwordReset,
|
||||
};
|
||||
}
|
||||
|
||||
// Passwort neu generieren und beim Provider setzen
|
||||
export async function resetMailboxPassword(id: number): Promise<{ success: boolean; password?: string; error?: string }> {
|
||||
const stressfreiEmail = await prisma.stressfreiEmail.findUnique({
|
||||
|
||||
@@ -97,6 +97,33 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
|
||||
|
||||
## ✅ Erledigt
|
||||
|
||||
- [x] **🔁 Stressfrei-Adressen: Weiterleitungen + Passwort manuell synchronisieren**
|
||||
- Refresh-Icon-Button in der Action-Reihe jeder Stressfrei-Adresse
|
||||
(Tooltip erklärt: „ersetzt die Forwards am Provider durch
|
||||
Kunden-Stamm-E-Mail + Service-Adresse"). Use-Case: nach Änderung der
|
||||
Stamm-E-Mail eines Kunden, oder nach Wechsel der
|
||||
`defaultForwardEmail` in den Provider-Settings.
|
||||
- **Bei `hasMailbox: true`** wird zusätzlich das im CRM verschlüsselt
|
||||
hinterlegte Mailbox-Passwort am Provider neu gesetzt. Self-Healing
|
||||
für den Fall, dass jemand im Plesk-UI manuell ein anderes Passwort
|
||||
gesetzt hat und IMAP/SMTP im CRM nicht mehr passt.
|
||||
- Backend nutzt Plesk's `updateForwardTargets` (`set:email1,email2`
|
||||
→ ersetzt komplett, idempotent) + bei Mailbox auch
|
||||
`updateMailboxPassword` (Plesk-Passwort-Update).
|
||||
- Endpoint: `POST /api/stressfrei-emails/:id/sync-forwarding`,
|
||||
`customers:update`-Permission, Audit-Log mit Forward-Targets +
|
||||
Passwort-Reset-Marker.
|
||||
- Self-Healing: `isProvisioned`-Flag wird bei erfolgreichem
|
||||
Provider-Aufruf automatisch auf `true` korrigiert (historischer Bug:
|
||||
Flag wurde beim `createEmail` mit `provisionAtProvider: true` nie
|
||||
gesetzt – jetzt behoben + Backfill via Sync).
|
||||
- Erfolgs-/Fehler-Meldungen via `react-hot-toast` (statt `alert()`)
|
||||
mit Liste der gesetzten Forward-Targets + Hinweis ob Passwort-Reset
|
||||
durchgeführt wurde.
|
||||
- In der Kundenakte (Stammdaten → Kontakt → E-Mail) externes
|
||||
Link-Icon, das in neuem Tab direkt den Stressfrei-Tab des Kunden
|
||||
öffnet – sichtbar nur wenn Stressfrei-Adressen vorhanden sind.
|
||||
|
||||
- [x] **🛡️ Pentest-Hardening-Runde 11: Header-Hygiene**
|
||||
- **HSTS-Doppel-Header** (18× low im Audit): Helmet's
|
||||
`Strict-Transport-Security` komplett deaktiviert. Der Nginx Proxy Manager
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
|
||||
import { useParams, Link, useNavigate, useSearchParams, useLocation } from 'react-router-dom';
|
||||
import { pushHistory, popHistory } from '../../utils/navigation';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import toast from 'react-hot-toast';
|
||||
import { customerApi, addressApi, bankCardApi, documentApi, meterApi, uploadApi, contractApi, stressfreiEmailApi, emailProviderApi, gdprApi, StressfreiEmail, ContractTreeNode } from '../../services/api';
|
||||
import { EmailClientTab } from '../../components/email';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
@@ -13,7 +14,7 @@ import Modal from '../../components/ui/Modal';
|
||||
import Input from '../../components/ui/Input';
|
||||
import Select from '../../components/ui/Select';
|
||||
import FileUpload from '../../components/ui/FileUpload';
|
||||
import { Edit, Plus, Trash2, MapPin, CreditCard, FileText, Gauge, Eye, EyeOff, Download, Globe, UserPlus, X, Search, Mail, Copy, Check, ChevronDown, ChevronRight, Info, Shield, ShieldCheck, ShieldX, ShieldAlert, Lock, ArrowLeft, Cake } from 'lucide-react';
|
||||
import { Edit, Plus, Trash2, MapPin, CreditCard, FileText, Gauge, Eye, EyeOff, Download, Globe, UserPlus, X, Search, Mail, Copy, Check, ChevronDown, ChevronRight, Info, Shield, ShieldCheck, ShieldX, ShieldAlert, Lock, ArrowLeft, Cake, RefreshCw, ExternalLink } from 'lucide-react';
|
||||
import CopyButton, { CopyableBlock } from '../../components/ui/CopyButton';
|
||||
import BirthdayManagementModal from '../../components/BirthdayManagementModal';
|
||||
import { formatDate } from '../../utils/dateFormat';
|
||||
@@ -353,6 +354,17 @@ export default function CustomerDetail({ portalCustomerId }: { portalCustomerId?
|
||||
{c.email}
|
||||
</a>
|
||||
<CopyButton value={c.email} />
|
||||
{(c.stressfreiEmails?.length ?? 0) > 0 && (
|
||||
<a
|
||||
href={`/customers/${c.id}?tab=stressfrei`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-gray-400 hover:text-blue-600 ml-1"
|
||||
title="Stressfrei-Wechseln-Adressen öffnen (neuer Tab). Nach Änderung der Stamm-E-Mail dort die Weiterleitungen synchronisieren."
|
||||
>
|
||||
<ExternalLink className="w-3.5 h-3.5" />
|
||||
</a>
|
||||
)}
|
||||
</dd>
|
||||
</div>
|
||||
)}
|
||||
@@ -2964,6 +2976,31 @@ function StressfreiEmailsTab({
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['customer', customerId] }),
|
||||
});
|
||||
|
||||
// Weiterleitungen am Provider neu setzen (Stamm-Email-Wechsel-Use-Case).
|
||||
// Wenn die Adresse hasMailbox=true ist, wird zusätzlich das im CRM
|
||||
// hinterlegte Passwort am Provider neu gesetzt (Self-Healing nach
|
||||
// manuellen Eingriffen am Provider).
|
||||
const syncForwardingMutation = useMutation({
|
||||
mutationFn: stressfreiEmailApi.syncForwarding,
|
||||
onSuccess: (res) => {
|
||||
const targets = res?.data?.forwardTargets || [];
|
||||
const passwordReset = res?.data?.passwordReset;
|
||||
const lines = [
|
||||
'Weiterleitungen aktualisiert:',
|
||||
...targets.map((t) => `• ${t}`),
|
||||
];
|
||||
if (passwordReset) lines.push('Mailbox-Passwort am Provider neu gesetzt.');
|
||||
toast.success(lines.join('\n'), { duration: 5000 });
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId] });
|
||||
},
|
||||
onError: (err: any) => {
|
||||
toast.error(
|
||||
err?.response?.data?.error || err?.message || 'Fehler beim Aktualisieren der Weiterleitungen',
|
||||
{ duration: 6000 },
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const filtered = showInactive ? emails : emails.filter((e) => e.isActive);
|
||||
|
||||
return (
|
||||
@@ -3023,6 +3060,40 @@ function StressfreiEmailsTab({
|
||||
>
|
||||
<Edit className="w-4 h-4" />
|
||||
</Button>
|
||||
{emailItem.isProvisioned && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
disabled={syncForwardingMutation.isPending}
|
||||
onClick={() => {
|
||||
const lines = [
|
||||
`Weiterleitungen für ${emailItem.email} jetzt neu setzen?`,
|
||||
'',
|
||||
'Alle bestehenden Weiterleitungen am Provider werden ersetzt durch:',
|
||||
'• die aktuelle Stamm-E-Mail des Kunden',
|
||||
'• unsere Service-Weiterleitungsadresse aus den Provider-Einstellungen',
|
||||
];
|
||||
if (emailItem.hasMailbox) {
|
||||
lines.push(
|
||||
'',
|
||||
'Zusätzlich wird das im CRM hinterlegte Mailbox-Passwort am Provider neu gesetzt.',
|
||||
);
|
||||
}
|
||||
if (confirm(lines.join('\n'))) {
|
||||
syncForwardingMutation.mutate(emailItem.id);
|
||||
}
|
||||
}}
|
||||
title={
|
||||
emailItem.hasMailbox
|
||||
? 'Weiterleitungen + Mailbox-Passwort synchronisieren. Nützlich nach Änderung der Kunden-Stamm-E-Mail oder nach manuellem Eingriff am Provider.'
|
||||
: 'Weiterleitungen synchronisieren – ersetzt die Forwards am Provider durch (Kunden-Stamm-E-Mail + Service-Adresse). Nützlich nach Änderung der Stamm-E-Mail.'
|
||||
}
|
||||
>
|
||||
<RefreshCw
|
||||
className={`w-4 h-4 ${syncForwardingMutation.isPending ? 'animate-spin' : ''}`}
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
{emailItem.isActive ? (
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
||||
@@ -268,6 +268,7 @@ export interface StressfreiEmail {
|
||||
platform?: string;
|
||||
notes?: string;
|
||||
isActive: boolean;
|
||||
isProvisioned?: boolean;
|
||||
hasMailbox: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
@@ -426,6 +427,17 @@ export const stressfreiEmailApi = {
|
||||
const res = await api.post<ApiResponse<{ password: string }>>(`/stressfrei-emails/${id}/reset-password`);
|
||||
return res.data;
|
||||
},
|
||||
// Weiterleitungen neu setzen (z.B. nach Änderung der Kunden-Stamm-E-Mail).
|
||||
// Wenn die Adresse hasMailbox=true ist, wird zusätzlich das im CRM
|
||||
// hinterlegte Passwort am Provider neu gesetzt (Self-Healing).
|
||||
syncForwarding: async (id: number) => {
|
||||
const res = await api.post<ApiResponse<{
|
||||
forwardTargets: string[];
|
||||
customerEmail: string;
|
||||
passwordReset?: boolean;
|
||||
}>>(`/stressfrei-emails/${id}/sync-forwarding`);
|
||||
return res.data;
|
||||
},
|
||||
// E-Mails synchronisieren
|
||||
syncEmails: async (id: number, fullSync = false) => {
|
||||
const res = await api.post<ApiResponse<SyncResult>>(`/stressfrei-emails/${id}/sync`, {}, { params: { full: fullSync } });
|
||||
|
||||
Reference in New Issue
Block a user