robust charset decoding + retry-failed endpoint

- fix: fetch_mail crashed on MIME pseudo-encodings like 'unknown-8bit',
  'x-unknown', '8bit'. New _safe_decode helper maps those (and any
  LookupError from unknown codecs) to latin-1, which never fails on
  8-bit input. Used in _extract_body and _decode_header_value.
- Consequence of the crash: scheduler marked the affected UIDs as
  processed to avoid retry loops, so those mails never got sorted
  even after the underlying issue was fixable.
- feat: POST /api/filters/retry-failed drops the ProcessedMail markers
  for UIDs that appear in "Fehler beim Abrufen" error logs, so the
  next poll re-evaluates them. Reachable via a button in the log UI
  ("Fehlgeschlagene neu einlesen"), optionally scoped per account.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-01 11:32:54 +02:00
co-authored by Claude Opus 4.7
parent e4669cfccd
commit 54183fdc3a
3 changed files with 74 additions and 6 deletions
+15
View File
@@ -29,6 +29,7 @@
</label>
<div role="group" style="margin-bottom:0;">
<button class="outline small" onclick="loadLogs()">Suchen</button>
<button class="outline small" onclick="retryFailed()" title="Löscht den 'verarbeitet'-Marker für Mails, die einen Fetch-Fehler hatten, damit sie beim nächsten Poll neu versucht werden">Fehlgeschlagene neu einlesen</button>
<button class="outline small contrast" onclick="clearLogs()">Log leeren</button>
</div>
<label style="margin-bottom:0;">
@@ -236,6 +237,20 @@ async function clearLogs() {
loadLogs();
}
async function retryFailed() {
if (!confirm('Alle Mails mit "Fehler beim Abrufen" für den nächsten Poll neu vormerken?')) return;
const accountId = document.getElementById('log-account').value;
let url = '/api/filters/retry-failed';
if (accountId) url += `?account_id=${accountId}`;
try {
const resp = await fetch(url, {method: 'POST'});
const data = await resp.json();
alert(`${data.unique_uids || 0} UID(s) neu vorgemerkt (${data.reset || 0} ProcessedMail-Einträge gelöscht). Werden beim nächsten Poll neu versucht.`);
} catch(e) {
alert('Fehler: ' + e.message);
}
}
function toggleAutoRefresh() {
if (document.getElementById('auto-refresh').checked) {
refreshTimer = setInterval(() => loadLogs(currentOffset), 5000);