From 13af757ad19538afbead93cca6d180517fdff498 Mon Sep 17 00:00:00 2001 From: jubnl Date: Tue, 14 Apr 2026 16:14:58 +0200 Subject: [PATCH 1/2] fix(notifications): fix SMTP error surfacing, webhook button label, backup timestamp - testSmtp now surfaces real nodemailer error instead of generic 'SMTP not configured' on send failure - admin webhook test button uses correct i18n key (was showing 'Test-E-Mail senden' in all languages) - backup created_at uses stat.mtime instead of unreliable stat.birthtime on Linux --- client/src/pages/AdminPage.tsx | 2 +- server/src/services/backupService.ts | 2 +- server/src/services/notifications.ts | 19 +++++++++++++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/client/src/pages/AdminPage.tsx b/client/src/pages/AdminPage.tsx index 062a3650..da4e28ef 100644 --- a/client/src/pages/AdminPage.tsx +++ b/client/src/pages/AdminPage.tsx @@ -1353,7 +1353,7 @@ export default function AdminPage(): React.ReactElement { disabled={!smtpValues.admin_webhook_url?.trim()} className="px-4 py-2 border border-slate-300 text-slate-700 rounded-lg text-sm font-medium hover:bg-slate-50 transition-colors disabled:opacity-40" > - {t('admin.smtp.testButton')} + {t('admin.notifications.testWebhook')} diff --git a/server/src/services/backupService.ts b/server/src/services/backupService.ts index cfc8f976..0075521b 100644 --- a/server/src/services/backupService.ts +++ b/server/src/services/backupService.ts @@ -117,7 +117,7 @@ export function listBackups(): BackupInfo[] { filename, size: stat.size, sizeText: formatSize(stat.size), - created_at: stat.birthtime.toISOString(), + created_at: stat.mtime.toISOString(), }; }) .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); diff --git a/server/src/services/notifications.ts b/server/src/services/notifications.ts index 56363404..db337177 100644 --- a/server/src/services/notifications.ts +++ b/server/src/services/notifications.ts @@ -399,9 +399,24 @@ export async function sendWebhook(url: string, payload: { event: string; title: } export async function testSmtp(to: string): Promise<{ success: boolean; error?: string }> { + if (!getSmtpConfig()) return { success: false, error: 'SMTP not configured' }; try { - const sent = await sendEmail(to, 'Test Notification', 'This is a test email from TREK. If you received this, your SMTP configuration is working correctly.'); - return sent ? { success: true } : { success: false, error: 'SMTP not configured' }; + const config = getSmtpConfig()!; + const skipTls = process.env.SMTP_SKIP_TLS_VERIFY === 'true' || getAppSetting('smtp_skip_tls_verify') === 'true'; + const transporter = nodemailer.createTransport({ + host: config.host, + port: config.port, + secure: config.secure, + auth: config.user ? { user: config.user, pass: config.pass } : undefined, + ...(skipTls ? { tls: { rejectUnauthorized: false } } : {}), + }); + await transporter.sendMail({ + from: config.from, + to, + subject: 'TREK — Test Notification', + text: 'This is a test email from TREK. If you received this, your SMTP configuration is working correctly.', + }); + return { success: true }; } catch (err) { return { success: false, error: err instanceof Error ? err.message : 'Unknown error' }; } From 6a23118342394b58f60003bd0a7cbd98d19f341e Mon Sep 17 00:00:00 2001 From: jubnl Date: Tue, 14 Apr 2026 16:14:58 +0200 Subject: [PATCH 2/2] fix(notifications): fix SMTP error surfacing, webhook button label, backup timestamp - testSmtp now surfaces real nodemailer error instead of generic 'SMTP not configured' on send failure - admin webhook test button uses correct i18n key (was showing 'Test-E-Mail senden' in all languages) - backup created_at uses stat.mtime instead of unreliable stat.birthtime on Linux --- client/src/pages/AdminPage.tsx | 2 +- server/src/services/backupService.ts | 2 +- server/src/services/notifications.ts | 19 +++++++++++++++++-- .../tests/unit/services/backupService.test.ts | 8 ++++---- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/client/src/pages/AdminPage.tsx b/client/src/pages/AdminPage.tsx index 062a3650..da4e28ef 100644 --- a/client/src/pages/AdminPage.tsx +++ b/client/src/pages/AdminPage.tsx @@ -1353,7 +1353,7 @@ export default function AdminPage(): React.ReactElement { disabled={!smtpValues.admin_webhook_url?.trim()} className="px-4 py-2 border border-slate-300 text-slate-700 rounded-lg text-sm font-medium hover:bg-slate-50 transition-colors disabled:opacity-40" > - {t('admin.smtp.testButton')} + {t('admin.notifications.testWebhook')} diff --git a/server/src/services/backupService.ts b/server/src/services/backupService.ts index cfc8f976..0075521b 100644 --- a/server/src/services/backupService.ts +++ b/server/src/services/backupService.ts @@ -117,7 +117,7 @@ export function listBackups(): BackupInfo[] { filename, size: stat.size, sizeText: formatSize(stat.size), - created_at: stat.birthtime.toISOString(), + created_at: stat.mtime.toISOString(), }; }) .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); diff --git a/server/src/services/notifications.ts b/server/src/services/notifications.ts index 56363404..db337177 100644 --- a/server/src/services/notifications.ts +++ b/server/src/services/notifications.ts @@ -399,9 +399,24 @@ export async function sendWebhook(url: string, payload: { event: string; title: } export async function testSmtp(to: string): Promise<{ success: boolean; error?: string }> { + if (!getSmtpConfig()) return { success: false, error: 'SMTP not configured' }; try { - const sent = await sendEmail(to, 'Test Notification', 'This is a test email from TREK. If you received this, your SMTP configuration is working correctly.'); - return sent ? { success: true } : { success: false, error: 'SMTP not configured' }; + const config = getSmtpConfig()!; + const skipTls = process.env.SMTP_SKIP_TLS_VERIFY === 'true' || getAppSetting('smtp_skip_tls_verify') === 'true'; + const transporter = nodemailer.createTransport({ + host: config.host, + port: config.port, + secure: config.secure, + auth: config.user ? { user: config.user, pass: config.pass } : undefined, + ...(skipTls ? { tls: { rejectUnauthorized: false } } : {}), + }); + await transporter.sendMail({ + from: config.from, + to, + subject: 'TREK — Test Notification', + text: 'This is a test email from TREK. If you received this, your SMTP configuration is working correctly.', + }); + return { success: true }; } catch (err) { return { success: false, error: err instanceof Error ? err.message : 'Unknown error' }; } diff --git a/server/tests/unit/services/backupService.test.ts b/server/tests/unit/services/backupService.test.ts index b1f7f1cb..081f27aa 100644 --- a/server/tests/unit/services/backupService.test.ts +++ b/server/tests/unit/services/backupService.test.ts @@ -580,7 +580,7 @@ describe('BACKUP-041 listBackups', () => { fsMock.readdirSync.mockReturnValue(['backup-2026-01-01T00-00-00.zip']); fsMock.statSync.mockReturnValue({ size: 1024, - birthtime: new Date('2026-01-01T00:00:00Z'), + mtime: new Date('2026-01-01T00:00:00Z'), }); const result = listBackups(); @@ -599,9 +599,9 @@ describe('BACKUP-041 listBackups', () => { ]); fsMock.statSync.mockImplementation((p: string) => { if (String(p).includes('2026-01-01')) { - return { size: 512, birthtime: new Date('2026-01-01T00:00:00Z') }; + return { size: 512, mtime: new Date('2026-01-01T00:00:00Z') }; } - return { size: 2048, birthtime: new Date('2026-06-01T00:00:00Z') }; + return { size: 2048, mtime: new Date('2026-06-01T00:00:00Z') }; }); const result = listBackups(); @@ -619,7 +619,7 @@ describe('BACKUP-041 listBackups', () => { ]); fsMock.statSync.mockReturnValue({ size: 1024, - birthtime: new Date('2026-01-01T00:00:00Z'), + mtime: new Date('2026-01-01T00:00:00Z'), }); const result = listBackups();