Redis streams keep the message; a list does not

BLPOP is atomic and destructive, so a consumer that crashes between popping and finishing has lost the message with no record that it existed.

# list: in flight means gone
RPUSH jobs '{"order":91204}'
BLPOP jobs 0

# stream: append-only, entries have ids, reading does not remove
XADD orders '*' order_id 91204 total 4900
XLEN orders
XRANGE orders - + COUNT 10

The * generates an id from the millisecond plus a sequence number, so ids are unique, sortable and roughly meaningful as timestamps. Entries persist after being read, which is what makes replay and multiple independent consumers possible — and also means the stream grows forever unless something trims it. Fields are flat key-value pairs, so anything structured is still your own serialisation problem.