Skip to content

Inbox

The envelope in the main menu top bar opens the inbox: messages the team sends to every player, such as an update going out or a bug being fixed. A message can carry a cash reward, which each player can claim once while the message runs. New players who open the game before a message ends can claim it too. The design is in docs/2026-11-08-achievements-inbox-design.md.

Messages sit in the message table on the race server’s D1 (party/migrations/0009_message.sql): title, body, reward_cash, and starts_at and expires_at in ms since the epoch. The table holds no player data, so the account delete and export (party/src/db/userData.ts) leave it alone.

What a player read and claimed lives in their save as inbox: { readUpTo, claimed }. claimed only keeps ids of messages still running, so it stays short. The game’s sync never sends it: only the server writes it.

player/inbox.ts holds them, and the server runs them inside changeGarage:

  • hasNews: the envelope’s red dot, for a message newer than readUpTo or a reward not claimed yet.
  • markRead: opening the inbox moves readUpTo to the newest running message.
  • claimReward: refuses a message that ended, has no reward or was claimed already; otherwise adds the cash.

tests/inbox.test.ts covers them; npm --prefix party run test:inbox checks the endpoints against a running wrangler dev.

party/src/api/inboxApi.ts, signed in:

Endpoint Does
GET /api/inbox The running messages, newest first.
POST /api/inbox/read Marks them read; answers with the garage.
POST /api/inbox/claim { id } Pays the reward; answers with the garage, or 409 and why not.
Terminal window
npm --prefix party run message:send -- --title "Nitrous fix is out" --body "..." --reward 2000 --days 14

It inserts into the remote D1 (--local for the database npm run dev uses). --reward is whole dollars, 0 for news only. Write the title and body through rsp:write-content first: they go to every player as they are.

  • Envelope (menu/topBar/inboxButton.ts): fetches the running messages when the menu opens and adds the red dot when hasNews says so. Offline it stays a plain envelope.
  • Inbox screen (inbox/scenes/Inbox.ts, its own feature module): the messages as cards (inbox/view/messageCard.ts), a Claim button on each unclaimed reward. Messages that do not fit the panel are left out; they run out anyway.