From fb48ba4cb4cbd2ec5836fc3adaffb79ea8d20787 Mon Sep 17 00:00:00 2001 From: Luca <102960244+Luca-Timo@users.noreply.github.com> Date: Fri, 12 Jun 2026 12:33:04 +0200 Subject: [PATCH] fix(email): mark required fields on incoming mail to match outgoing SMTP The IMAP card was restyled to match SMTP but didn't carry the required-field markers. Aligned the required set (protocol differences kept): - red asterisks on Host *, Port *, Username * (SMTP marks Host/Port/From-Email; IMAP has no From-Email but always needs a login) - client-side guard mirroring handleSaveSmtp (block save without host/port/user) - backend POST /incoming-config now requires imap_user (the poller's getImapConfig returns null without it) - en/de requiredFields string --- backend/src/routes/adminEmail.js | 3 +++ .../components/admin/IncomingMailConfigCard.tsx | 16 ++++++++++++---- frontend/src/i18n/locales/de.json | 1 + frontend/src/i18n/locales/en.json | 1 + 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/backend/src/routes/adminEmail.js b/backend/src/routes/adminEmail.js index da7f4f20..7f815d83 100644 --- a/backend/src/routes/adminEmail.js +++ b/backend/src/routes/adminEmail.js @@ -141,6 +141,9 @@ router.post('/incoming-config', [ requirePermission('email.edit'), body('imap_host').notEmpty().withMessage('IMAP host is required'), body('imap_port').isInt({ min: 1, max: 65535 }).withMessage('Invalid port number'), + // IMAP always needs a login (unlike SMTP relay) — the poller's + // getImapConfig() returns null without a username, so require it. + body('imap_user').notEmpty().withMessage('IMAP username is required'), ], async (req, res) => { try { const errors = validationResult(req); diff --git a/frontend/src/components/admin/IncomingMailConfigCard.tsx b/frontend/src/components/admin/IncomingMailConfigCard.tsx index c9d2f10f..5cf2fe5f 100644 --- a/frontend/src/components/admin/IncomingMailConfigCard.tsx +++ b/frontend/src/components/admin/IncomingMailConfigCard.tsx @@ -31,7 +31,15 @@ export const IncomingMailConfigCard: React.FC = () => { const set = (k: keyof IncomingMailConfig, v: any) => setCfg((c) => ({ ...c, [k]: v })); const save = useMutation({ - mutationFn: () => emailService.updateIncomingConfig(cfg), + mutationFn: () => { + // Mirror the SMTP card's client-side required guard. Host + port + + // username are needed for the poller to authenticate (getImapConfig + // returns null without host+user). + if (!cfg.imap_host || !cfg.imap_port || !cfg.imap_user) { + return Promise.reject(new Error(t('email.incoming.requiredFields', 'Host, port and username are required.'))); + } + return emailService.updateIncomingConfig(cfg); + }, onSuccess: () => { toast.success(t('email.incoming.savedToast', 'Incoming mail settings saved.')); qc.invalidateQueries({ queryKey: ['incoming-mail-config'] }); }, onError: (e: any) => toast.error(e?.response?.data?.error || e?.response?.data?.errors?.[0]?.msg || e.message || 'Failed'), }); @@ -66,7 +74,7 @@ export const IncomingMailConfigCard: React.FC = () => {