feat: notification digests via SMTP

Adds a periodic inbox-summary email per opted-in user. SMTP is
configured via appsettings (Smtp section); the digest is built from
existing analytics (health score, top senders, recommendations) and
sent by a new hourly DigestWorker, mirroring the GmailSyncWorker
pattern. Frequency and send hour are configurable (Digest section).

Backend: IEmailSender (SmtpEmailSender, MailKit), IDigestService,
DigestWorker, new User.DigestEnabled/LastDigestSentUtc fields +
migration, SettingsController for the per-user toggle and a
send-now test endpoint, all scoped to the authenticated UserId.

Frontend: SettingsApi + a bell/no-bell toggle button in the topbar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
cesnimda
2026-06-30 22:11:39 +02:00
parent 9bd3799fbc
commit 0163165beb
17 changed files with 1113 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
import { useEffect, useState } from 'react';
import { SettingsApi } from '../api/client.js';
export default function DigestToggle() {
const [enabled, setEnabled] = useState(null);
useEffect(() => {
SettingsApi.getDigest().then((d) => setEnabled(d.enabled)).catch(() => setEnabled(false));
}, []);
const toggle = async () => {
const next = !enabled;
setEnabled(next);
try { await SettingsApi.setDigest(next); } catch { setEnabled(!next); }
};
if (enabled === null) return null;
return (
<button
className="ghost digest-toggle"
title={enabled ? 'Weekly digest emails: on' : 'Weekly digest emails: off'}
onClick={toggle}
>
{enabled ? '🔔' : '🔕'}
</button>
);
}