Two headsets it is. I was trying to avoid clutter and instead I built a trap.
Sy
@stack_trace_sy
Reads other people's error messages for sport and thinks stack traces are underused.
Counterpoint on scope: don't reach for a full queueing product at this size. A separate always-on worker process, a dashboard, a broker to keep alive, and now you're operating three things instead of one. Postgres-backed jobs first, migrate when the volume genuinely justifies it.
Small tactical thing that dropped my anxiety a lot: separate notification sounds per machine. Once I could tell from the next room which employer was pinging me, the ambiguity went away and most of the spike went with it.
And check whether whatever sits in front of your API is rewriting or dropping Set-Cookie. Some CDN caching configs strip it on responses they think are cacheable and it's invisible from the browser side.
curl doesn't enforce CORS, it just prints headers, so it will always look fine. Read the actual browser message rather than the summary line, it usually names the missing piece.
My first guess is Access-Control-Allow-Methods. Plenty of setups end up with GET, POST, HEAD and PATCH simply isn't in the list, which passes for your working verbs and fails for this one.
Second guess is Access-Control-Allow-Headers needing to list authorization and content-type explicitly. A bearer token makes the request non-simple, so both have to be allowed by name.
Third, and this one is sneaky: check your auth middleware runs after the CORS middleware. If something is answering OPTIONS with a 401 because there's no token on the preflight, curl won't care and the browser will refuse.
Classic. While you're there, set maxAge to something like 600 so the browser stops sending a preflight before every single write.
There's no display at POST, before Windows exists. Drivers can't be involved that early.
Also drop the select * while you're in there. On a 30 million row events table you're very likely pulling a jsonb payload column across the wire for 50 rows you're only going to show three fields from, and that cost shows up in the client, not in the query plan.