On a client marketplace, companies list, negotiate and manage contracts. The API is NestJS. PostgreSQL, through MikroORM, is where the contracts live. Redis sits on the path for work that has to be fast and can be repeated: the events behind negotiation chat, and the handoff for emails and notifications.
Contract search is not a PostgreSQL query. Filters go through Meilisearch. Chat and support tickets are Socket.IO on that same API, not a second service.
The failure that mattered was Redis being down. A message that existed only as a publish to Redis never left. The outbox is the fix. The API writes the message in PostgreSQL in the same transaction as the contract change it belongs to. A worker reads the rows that have not been delivered, publishes them when Redis is accepting work, and marks them delivered. If the process dies halfway, the row is still there and the next pass sends it. A deal can commit while Redis is unavailable. The message waits.
That is the split. PostgreSQL is the record. Redis is a way to deliver work, and it is allowed to disappear. Anything a person was promised, an email or a notification, is written down before it is sent.
Regulatory reports, and the Excel exports of contract history, are read from PostgreSQL. They do not depend on Redis or on the search index.
The screens in front of this API are Next.js. What stayed out of those apps is the same boundary.