retry-failed: only pick open errors and mark them as retried

"Fehlgeschlagene neu einlesen" reported the same 29 mails on every
click: it selected all error log entries ever written and never noted
that one had already been retried.

Error log entries now get retried_at when they are picked up; the
endpoint only considers entries without it. If the mail fails again,
the scheduler writes a new, open error entry, so a second click right
after the first reports 0. The log shows retried errors greyed out
with "↻ erneut versucht <Zeit>", and the result dialog says how many
open error entries and mails were requeued.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-29 12:12:03 +02:00
co-authored by Claude Opus 5.5
parent fbb2fe95c8
commit 360feea42e
5 changed files with 77 additions and 16 deletions
@@ -0,0 +1,29 @@
"""add retried_at to filter_logs
Revision ID: b4e8d2f61a07
Revises: 9a3f5c1d7e42
Create Date: 2026-09-29 00:00:00.000000
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = "b4e8d2f61a07"
down_revision: Union[str, Sequence[str], None] = "9a3f5c1d7e42"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Markiert Fehler-Einträge, die bereits erneut angestoßen wurden, damit
'Fehlgeschlagene neu einlesen' nicht immer wieder dieselben aufgreift.
Nullable-Spalte → SQLite kann sie ohne Tabellen-Neuaufbau anhängen."""
op.add_column("filter_logs", sa.Column("retried_at", sa.DateTime(), nullable=True))
def downgrade() -> None:
with op.batch_alter_table("filter_logs", schema=None) as batch_op:
batch_op.drop_column("retried_at")