Open source · works offline

Your local development
sidekick for Upstash.

downstash is a single binary that mocks Upstash QStash and Upstash Redis on your laptop — same HTTP wire shape, same SDKs, zero internet. Develop on planes. Run integration tests in CI without a credential.

0 tunnels
0 credentials
~250ms tick loop
SQLite persistence
~/projects/my-app — downstash
Why downstash

Local Upstash, without the workarounds.

QStash can't reach localhost. Redis-over-HTTP needs internet for every request. We built the sidekick that closes both gaps.

No tunnels.

Skip ngrok, env var juggling, and the dance of remembering to undo it before you commit. downstash speaks plain http://localhost.

Works offline.

Plane, train, hotel wifi, blackout — fine. Your test suite stops depending on a remote service that can rate-limit, retry, or simply be down.

CI-friendly.

Spin it up in a workflow step. Run integration tests against QStash and Redis with no external dependency, no flaky network, no shared sandbox.

Before The usual local dev loop
  • Spin up ngrok, copy URL, paste into env
    Repeat every session. Don't forget to remove it.
  • Hit a remote Redis for every test
    Slow iteration, flaky CI, real latency in unit tests.
  • Burn through QStash quota in dev
    Real messages, real billing.
  • No way to inspect or replay messages
    Hope your logs are good.
With downstash One process, two services
  • Point your SDKs at localhost:8080
    Same constructors, no code changes.
  • In-memory Redis with full command coverage
    Strings, hashes, lists, sets, sorted sets, pipelines.
  • Real signed JWTs, verified by the Upstash SDK
    Signing works exactly like production.
  • Inspect any message with one curl
    GET /v2/messages/:id — state, retries, body.
Quick start

Three commands. Two SDKs. Zero config drift.

Install once, drop the same .env.local into every project, and your existing Upstash code keeps working untouched.

# Requires Bun >= 1.1.0
$ git clone https://github.com/sskcfC15Xfoxd7X1sVFgipdzMRAkP/downstash
$ cd downstash
$ bun install
$ bun link            # makes the `downstash` command available globally

$ downstash
2026-04-29T12:00:00.000Z INFO  downstash listening port=8080 db=.downstash/db.sqlite tickMs=250
What's inside

Two services. One sidekick.

downstash mirrors the Upstash wire shape closely — same headers, same JWT signing, same encoding flags. Your code can't tell the difference.

QStash

Signed message delivery, locally.

Publish to any URL — including localhost — with delay, retries, callbacks, and HMAC-SHA256 signed JWTs that the official Receiver verifies cleanly.

publish & publishJSON
Upstash-Method override
Delay & Not-Before
Retries w/ exp. backoff
Per-attempt timeout
Forward-* headers
Success & failure callbacks
Batch fan-out
Inspect & cancel
SQLite persistence
1
POST /v2/publishJSON
your app
2
enqueue + sign JWT
downstash · sqlite
↓ tick 250ms
3
deliver to localhost:3000
retry · backoff
↓ on success/fail
4
callback envelope
re-enqueue
Redis

The commands you actually use, in-memory.

Strings, hashes, lists, sets, sorted sets, pipelines, transactions, TTLs, scans. The @upstash/redis SDK works without modification — same REST shape, same base64 encoding flag.

80+ commands across 7 types
EX / PX / EXAT / PXAT TTLs
Pipelines & multi-exec
SCAN / HSCAN / SSCAN / ZSCAN
Sorted set unions/intersects
Single, pipeline, URL-path APIs
Upstash-Encoding: base64
Resets on restart
How signing works

Real JWTs. Real verification.

downstash signs every outbound delivery with a real HMAC-SHA256 JWT. The official Upstash Receiver verifies it — no shortcuts, no test-only paths in your code.

Verified by the same SDK you'll ship to production.

Every claim matches Upstash's wire format. Pass the Upstash-Signature header into Receiver.verify() exactly as you would in prod — downstash returns a stable pair of keys so your .env.local never drifts between machines.

QSTASH_CURRENT_SIGNING_KEY = sig_downstash_current_dev_key_…
QSTASH_NEXT_SIGNING_KEY = sig_downstash_next_dev_key_…
CLI

Small surface. Every flag has an env var.

downstashStart the server (port 8080)
downstash serveExplicit serve subcommand
downstash resetTruncate the messages table
downstash keysPrint signing keys + Redis config
--port <n>env: DOWNSTASH_PORT
--db <path>env: DOWNSTASH_DB
--tick-ms <n>Delivery loop interval (default 250)
--log-level <lvl>debug · info · warn · error
--current-signing-keyOverride current QStash key
--next-signing-keyOverride next QStash key
--redis-token <s>Redis auth token (default "dev")
--quietShorthand for --log-level=warn

Get the sidekick. Skip the workarounds.

MIT licensed. Single binary. No telemetry, no account, no cloud — just a process on your machine that finally lets you build offline.

Clone the repo Read the quick start →