Nobody has started this yet — be first.
Business impact
OUTBOX_MAX_ATTEMPTS is the operator's explicit promise to a downstream integration partner (the bank/UPI rail) about how many times we'll retry a call before giving up and dead-lettering it for manual review. If the dispatcher silently allows one extra attempt beyond that, a downstream that's rate-limiting or already returning "permanently rejected" gets hit one more time than agreed -- small on its own, but it's exactly the kind of drift that breaks an SLA with a partner who's counting requests on their side.
Problem
With OUTBOX_MAX_ATTEMPTS=2, a row that fails on every call is only dead-lettered after its 3rd failed attempt, not its 2nd.
Current behavior
A row still shows status "pending" after exactly maxAttempts failed dispatch attempts, when it should already be "dead".
Expected behavior
A row should be dead-lettered exactly when attempts reaches maxAttempts -- no more.
Steps to reproduce
TEST_DATABASE_URL=postgresql://... go test ./tickets/tests/... -run TestTicket10 -v
Why this matters
retryOrDeadLetter computes attempts := r.attempts + 1 (the count including this failure) and compares it against d.maxAttempts to decide dead-letter vs. reschedule. The comparison operator determines whether the last permitted attempt is treated as the final one or as one that still gets a follow-up retry.
Suggested approach
Walk through the arithmetic by hand for maxAttempts = 1: after the first (and only allowed) failure, attempts is 1. Should that dead-letter immediately, or get one more try first? Compare the current operator against that expectation.
Acceptance criteria
Verification
TEST_DATABASE_URL=postgresql://... go test ./tickets/tests/... -run TestTicket10 -v
Hints (0/2)
Try it without hints first — the reading is the exercise.
Working on this ticket
Work on a branch named for the ticket — that's what you'll submit.
Branch off your fork
Sign in to create it automatically, or do it yourself:
$git checkout -b fix/outbox-dead-letter-off-by-oneFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/outbox-dead-letter-off-by-oneSubmit it below
Paste your fork URL and the branch name, with a short write-up of the root cause.
Questions
Ask about anything unclear in the ticket — the maintainer and anyone who has solved it can answer. Please don't post full solutions.
Sign in to ask a question or reply.
Sign indocker composepaystate/paystate/go.modreplace reliable => ../reliabledocker compose up --buildpaystate/context: ..docker-compose.ymlCREATED_TIMEOUTAUTHORIZED_TIMEOUTWork the tickets in tickets/docs/ (ticket-01 through ticket-25); each names one Go test in tickets/tests/:
TEST_DATABASE_URL=postgresql://... go test ./tickets/tests/... -run TestTicket01 -v # a single ticket
TEST_DATABASE_URL=postgresql://... ./tickets/run_tickets.sh # all 25, clean pass/fail summary
Tickets that don't touch Postgres (02, 03, 04, 05, 06, 14, 15, 24) run fine without TEST_DATABASE_URL set; the rest report FAIL with a "TEST_DATABASE_URL not set" message if it's missing. make test-race runs the full go test -race -p 1 ./... suite the same way.
Level 1
Fix a bug
Read existing behaviour, correct it.