fix(mail): forceFullSweep on domain-add + 30s idle tick

Domain/display-name adds now force a full re-scan so newly-added gambling
senders are caught immediately instead of waiting for the incremental UID
window. IMAP-idle NOOP tick lowered 2min -> 30s to close the Junk-folder gap
faster (Outlook drops straight into Junk, which IDLE does not watch).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
chahinebrini 2026-06-07 00:11:01 +02:00
parent d31e45e2a8
commit b757486579
3 changed files with 17 additions and 5 deletions

View File

@ -59,9 +59,12 @@ const DB_REFRESH_INTERVAL_MS = 5 * 60 * 1000; // 5 min — neue Connections en
// Trade-off: alle 10min full reconnect-cycle. Vertretbar.
const IDLE_RENEW_INTERVAL_MS = 10 * 60 * 1000; // 10 min
// NOOP-heartbeat alle 2min während IDLE: detect silent-drops (GMX-pattern).
// Wenn NOOP fehlschlägt → close → loop iteriert → reconnect.
const IDLE_NOOP_INTERVAL_MS = 2 * 60 * 1000; // 2 min
// NOOP-heartbeat alle 30s während IDLE: detect silent-drops (GMX-pattern) +
// Junk-Folder-Sweep-Frequenz. Vertretbar seit Phase-2-Inkremental-Scan:
// leere SEARCH-Response (keine neuen UIDs) → skip ohne IMAP-Fetch, günstig.
// Server-Headroom (CPX42) deckt die höhere Trigger-Rate ab.
// Kein Konflikt mit IDLE_RENEW_INTERVAL_MS (10min) — beide laufen unabhängig.
const IDLE_NOOP_INTERVAL_MS = 30 * 1000; // 30 s
// Token-Refresh-Schwelle: wenn Access-Token in weniger als 5min abläuft, vor
// dem IMAP-Connect refreshen. Verhindert Mid-Session-Expiry.

View File

@ -249,7 +249,11 @@ export default defineEventHandler(async (event) => {
$fetch("/api/mail/scan-internal", {
method: "POST",
headers: { "x-admin-secret": adminSecret },
body: { userId: user.id },
// forceFullSweep: Alt-Mails der gerade hinzugefügten Domain werden sofort
// erfasst — unabhängig davon ob lastFullSweepAt < 24h. Ohne dieses Flag
// würde der inkrementelle Pfad greifen (UIDs > lastUid) und bereits vor-
// handene Mails dieser Domain übersehen.
body: { userId: user.id, forceFullSweep: true },
}).catch((err: unknown) => {
// Fire-and-forget: Fehler loggen, aber POST-Response nicht blockieren.
// Der Scan ist best-effort — nächster Cron holt nach.

View File

@ -32,7 +32,7 @@ export default defineEventHandler(async (event) => {
throw createError({ statusCode: 401, message: "Unauthorized" });
}
const body = (await readBody(event)) as { userId?: string };
const body = (await readBody(event)) as { userId?: string; forceFullSweep?: boolean };
const userId = body?.userId;
if (!userId)
throw createError({ statusCode: 400, message: "userId missing" });
@ -102,7 +102,12 @@ export default defineEventHandler(async (event) => {
// zustellen dass Blocklist-Updates auch ältere Mails erfassen.
// Wichtig: wir behandeln lastUid temporär als 0 — NICHT persistieren.
// Nach dem Sweep wird der echte maxUid gespeichert + lastFullSweepAt=NOW().
//
// forceFullSweep: explizit erzwungener Full-Sweep (z.B. nach Custom-Domain-Add).
// Damit werden Alt-Mails der neuen Domain sofort erfasst, unabhängig davon
// ob der letzte Full-Sweep < 24h her ist.
const needsFullSweep =
body.forceFullSweep === true ||
!connection.lastFullSweepAt ||
Date.now() - new Date(connection.lastFullSweepAt).getTime() > 24 * 3_600_000;