move mails without Message-ID; log the reason for failed actions

A spam mail matched stefanhacker@gmx.de but "Aktion fehlgeschlagen"
every time: it has no Message-ID header, and the safe move refused to
proceed because it verifies the copy by Message-ID. So every mail
without that header stayed in INBOX forever, and the log didn't say why.

Verification now falls back when there is no Message-ID:
- COPYUID from the COPY response when the server supports UIDPLUS
- otherwise count mails in the target with the same exact size and
  sender before and after COPY; exactly one more means it arrived
The source is still only flagged/expunged after verification, and
move_mail now refuses to run without source_folder (the verification
selects the target, so STORE could otherwise hit the wrong folder).
Checked against a simulated IMAP server for success, rejected COPY,
copy not arriving, and missing source folder.

IMAPClient.last_error carries the reason (rejected COPY with server
text, copy not found incl. how it was checked, missing target folder,
no SMTP config, ...); the scheduler appends it to the log message.
"Fehlgeschlagene neu einlesen" now also retries failed actions, not
only fetch errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-29 12:02:30 +02:00
co-authored by Claude Opus 5.5
parent 7d03c62d8e
commit fbb2fe95c8
5 changed files with 131 additions and 60 deletions
+2 -2
View File
@@ -25,7 +25,7 @@
<ul dir="rtl">
<li class="db-status" id="db-status">Datenbank wird geprüft…</li>
<li><a href="#" onclick="runMaintenance(); return false;" title="Alte Einträge nach Aufbewahrungsregel löschen und die Datenbankdatei verkleinern">Datenbank aufräumen &amp; verkleinern</a></li>
<li><a href="#" onclick="retryFailed(); return false;" title="Löscht den 'verarbeitet'-Marker für Mails mit Abruf-Fehler, damit sie beim nächsten Poll neu versucht werden">Fehlgeschlagene neu einlesen</a></li>
<li><a href="#" onclick="retryFailed(); return false;" title="Mails mit Abruf-Fehler oder fehlgeschlagener Aktion beim nächsten Poll erneut verarbeiten">Fehlgeschlagene neu einlesen</a></li>
<li><a href="#" onclick="clearLogs(); return false;">Log leeren</a></li>
</ul>
</details>
@@ -435,7 +435,7 @@ async function clearLogs() {
async function retryFailed() {
document.querySelector('.head-menu').open = false;
if (!confirm('Alle Mails mit "Fehler beim Abrufen" für den nächsten Poll neu vormerken?')) return;
if (!confirm('Alle Mails, bei denen Abrufen oder eine Aktion (z.B. Verschieben) fehlgeschlagen ist, für den nächsten Poll neu vormerken?')) return;
const acc = document.getElementById('log-account').value;
const resp = await fetch(`/api/filters/retry-failed${acc ? `?account_id=${acc}` : ''}`, {method: 'POST'});
const d = await readJson(resp);