Nobody has started this yet — be first.
Business impact
Once an outbox row exhausts OUTBOX_MAX_ATTEMPTS and is marked dead, there's no way to give it another shot short of manual database surgery (UPDATE outbox SET status = 'pending', attempts = 0 WHERE id = ...) -- exactly the kind of thing that turns a 2-minute incident response into a "who has prod DB access and is awake right now" scramble.
Problem
The route (POST /v1/payments/{id}/outbox/{outboxID}/replay), handler, and error mapping (outbox.ErrOutboxRowNotFound -> 404) all exist; Dispatcher.ReplayDeadLetter itself is a panicking stub.
Current behavior
POST /v1/payments/{id}/outbox/{outboxID}/replay returns 500 with a panic logged: Dispatcher.ReplayDeadLetter not implemented.
Expected behavior
POST /v1/payments/{id}/outbox/{outboxID}/replay should requeue that specific row (only if it's currently dead, and only if it actually belongs to that payment) back to pending with attempts reset to 0, so the next dispatcher poll picks it up and retries the side effect fresh.
Steps to reproduce
go run ./cmd/paystate curl -s -X POST localhost:8080/v1/payments/<id>/outbox/<outboxID>/replay
Why this matters
This needs a somewhat careful WHERE clause: id = $1 AND payment_id = $2 AND status = 'dead'. All three predicates matter -- the id alone isn't enough (a caller could try to replay a row belonging to a different payment via the URL's {id}), and the status check matters because replaying a row that's pending/processing/processed would be a correctness bug (double-dispatching a row that's about to be or already was handled normally), not a feature.
Suggested approach
Look at Store.UpdateState's pattern of using RowsAffected() to detect "did my WHERE clause actually match a row" (in internal/payment/store.go) for the shape you need here: run the UPDATE ... WHERE id = $1 AND payment_id = $2 AND status = 'dead', and if RowsAffected() == 0, return ErrOutboxRowNotFound (declared right above ReplayDeadLetter) rather than treating it as success.
Acceptance criteria
Verification
TEST_DATABASE_URL=postgresql://... go test ./tickets/tests/... -run TestTicket25 -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 feat/outbox-replay-endpointFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin feat/outbox-replay-endpointSubmit 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 2
Implement a feature
Extend the system within its own patterns.