Build1 distinct publisher3 min readPublished
A social-media scheduler traced silently dead posts to a delivery JWT that QStash mints at enqueue time, and rebuilt dispatch so no message waits in the queue longer than its own signature stays valid.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
The 401 sits outside what the retry path can fix, which is what makes it terminal. QStash delivers on time; the postmortem, published on dev.to by the developer who runs the social-media scheduler PostStage, is explicit that delivery behaves exactly as documented [1][8]. What lands on the endpoint is a request whose JWT was minted when the message was enqueued, days earlier [6]. The handler calls `jose.jwtVerify()`, which checks `exp` and `nbf` with zero clock tolerance [7]. `Receiver.verify()` rejects, the endpoint answers 401, and QStash comes back three more times carrying the same token [9]. That is four attempts against one expired string [2]. Retries are built for transient faults, and an expired timestamp does not become valid on a second attempt.
The horizon where this appeared was roughly four to five days out, while one- and two-day schedules kept working normally [3][5]. Read that as a measurement of one account, not a documented limit, because the writeup never names the token's lifetime. For the number to transfer, your plan would need the same signature lifetime and your verifier would need to refuse clock skew the same way this one does.
The repair moves the far-future state out of the queue and into Postgres. A post inside `QSTASH_WINDOW_MS` is enqueued at creation; anything further out sits at status `SCHEDULED` with a null `qstashMessageId`, an ordinary resting state rather than a failure [10]. A dispatcher then sweeps for `SCHEDULED` rows due within the window and enqueues each one [11]. The default window is 24 hours, chosen because a daily cron already existed [12]. Against a failure horizon near 96 hours that leaves 72 hours of margin, so the longest any signature must survive is a quarter of the distance to where the 401s started [1]. None of this touches accuracy: the window governs only how long a signature ages before use, and QStash still fires at the scheduled second [14].
Tightening it further is where the bill arrives. The aggressive configuration points an external scheduler at a dedicated dispatch endpoint every five minutes and drops the window to about thirty minutes [13]. On free-tier Neon Postgres, that pattern consumed a month of compute allowance in days [17]. Neon autosuspends after roughly five minutes idle, and a driver on a five-minute cadence, 288 runs a day, never lets that gap open [18][3]. Autosuspend was the cost control, and the staleness fix disabled it as a side effect of closing the gap.
The two guards around the fix are the better engineering. The recovery pass looks for `QUEUED` posts well past due with zero publish-log rows, and it can trust that signal because the webhook's first action is always to write the "publishing started" row [15]. Zero rows means the handler never ran at all, which is a stronger fact than a missing side effect. The handler also claims each post atomically from `SCHEDULED` or `QUEUED` into `PUBLISHING`, so a redelivery or a recovery re-enqueue cannot publish the same post twice [16]. At-least-once delivery plus a re-enqueue sweep is precisely the combination that posts to somebody's Instagram twice.
The general form is worth keeping: a queue that signs at enqueue time has a maximum useful hold that is unrelated to its maximum documented delay [19]. If the docs are silent on when the signature is generated, enqueue time is the assumption that costs you least to be wrong about.
Ranked by verification strength, evidence, and original report placement.
The postmortem's author runs PostStage, a social-media scheduler that connects Instagram, LinkedIn, YouTube and Pinterest and lets a user write a post once and pick an exact publish time.
PostStage's scheduling engine is Upstash QStash: a message is enqueued with a notBefore timestamp, QStash holds it and delivers a webhook at that exact second, with no polling loop or cron guessing.
Posts scheduled roughly 4 to 5 days ahead came back from QStash as an HTTP 401 on delivery, retried three times, then landed in QStash's dead-letter queue; a slice of those posts silently died.
The affected posts eventually fired hours late, only because a separate recovery sweep happened to re-enqueue them.
The webhook worked fine for anything scheduled a day or two out, and nobody had touched auth code.
PostStage verifies the delivery signature with jose.jwtVerify(), which checks exp and nbf with zero clock tolerance.
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · September 2, 2026
Follow any of these and your For You feed starts watching them — no settings page required.
build
Prisma v7 stops seeding for you, and the pooled URL will not finish the job1 distinct publisher
build
SSE is 30 lines on one pod. The second pod is where the engineering starts1 distinct publisher
build
Edge KV puts the permission check an hour behind the Postgres row1 distinct publisher
build
Per-developer environments hit their ceiling the day one engineer ran five agents1 distinct publisher
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
One first-hand account, mechanically specific, externally unchecked
The internal detail is the kind you only get from someone who debugged the thing: the zero-tolerance verification call, the resting state with a null message id, the log row that proves a delivery never ran. What is missing is the one number the whole diagnosis turns on — how long a QStash delivery token stays valid. In its place sits an empirical 'roughly four to five days', with no Upstash documentation quoted and no second team reporting the same 401s.
Live on one product, echoed nowhere else
PostStage says the deferred-dispatch design runs its scheduler today, and the free-tier blowup is disclosed as lived experience rather than theory — that is genuine production use. It is also all of it. Nobody else in this reporting has reproduced the expiry, adopted the local-hold pattern, or filed it with the vendor.
Slightly undersold, deliberately
A piece like this could easily have been written as 'QStash loses your messages'. Instead it says twice that delivery worked exactly as documented and puts the failure in the author's own verifier. If anything the register is too calm: posts that vanish and only surface hours later because an unrelated sweep caught them is a harsher outcome than the tone implies. What holds the reading near zero is that the generalised warning to other queues rests on one unconfirmed vendor behaviour.
Founder marketing, pointed at its own vendors
The product is the byline: PostStage is introduced, credited with exact-second delivery three months out, and readers are invited into the comments. That is ordinary founder blogging, and it pulls against the usual vendor-flattery incentive — both Upstash's signing choice and Neon's free-tier billing come out with sharp edges. No sponsorship, affiliation, or credit arrangement is disclosed in either direction.
Coherent story, unverifiable by the reader
Symptom, cause and cure lock together without hand-waving: 401s only past a few days, a token minted at enqueue, a fix that shortens the hold rather than touching auth. Coherence is not corroboration, though. One author, one incident, no published expiry, no vendor acknowledgement — enough to act on cautiously if you run this stack, not enough to treat the four-day threshold as a constant.