Where: apps/api/Infrastructure/Messaging/Email/InvitationEmailOutboxDispatcher.cs:185
Problem: On a retryable provider failure, the row's NextAttemptAt is set (~2s later with backoff) but the pending predicate cannot see it, so it is only retried when the 5-minute processing lease expires. Every early retry is delayed to the lease boundary instead of using the advertised exponential backoff.
Fix: On retryable failure, atomically set Status = Pending, clear ClaimedAt, retain NextAttemptAt; keep Failed terminal. Add a ProcessBatchAsync spec that claims a real row, forces failure, reloads in a new context, and proves it is unclaimable before but claimable after NextAttemptAt.
Filed from the PR #806 review-fix loop (round 7, gpt-5.6-sol @ high). Branch feat/front-2-full-parity-handoff. Not a blocker; deferred for separate handling.
Where:
apps/api/Infrastructure/Messaging/Email/InvitationEmailOutboxDispatcher.cs:185Problem: On a retryable provider failure, the row's
NextAttemptAtis set (~2s later with backoff) but the pending predicate cannot see it, so it is only retried when the 5-minute processing lease expires. Every early retry is delayed to the lease boundary instead of using the advertised exponential backoff.Fix: On retryable failure, atomically set
Status = Pending, clearClaimedAt, retainNextAttemptAt; keepFailedterminal. Add aProcessBatchAsyncspec that claims a real row, forces failure, reloads in a new context, and proves it is unclaimable before but claimable afterNextAttemptAt.Filed from the PR #806 review-fix loop (round 7, gpt-5.6-sol @ high). Branch
feat/front-2-full-parity-handoff. Not a blocker; deferred for separate handling.