The ultimate self-hosted alternative to Upstash QStash.
Jeda is a lightning-fast, zero-config cloud task scheduler and cron jobs dispatcher. Delegate delayed webhook executions and timezone-aware cron jobs via a simple REST API, backed purely by Redis.
Jeda is designed to be 100% Plug & Play for single-tenant or internal team usage. No complex SQL databases or heavy dependencies—just a single statically linked Go binary and Redis.
| Component | Description |
|---|---|
| API Server | Receives tasks, handles authentication, and powers the Admin UI. |
| Worker | The engine that guarantees reliable delivery, retries, and cron executions. |
| Redis | The only state dependency. Handles queue storage and persistence. |
| Dashboard | Embedded Vue.js UI for real-time monitoring and task management. |
Pull from Docker Hub:
docker pull bagose/jeda-scheduler:latestRunning with Docker Compose:
Create a docker-compose.yml:
version: '3.8'
services:
jeda:
image: bagose/jeda-scheduler:latest
ports:
- "3001:3001"
volumes:
- ./.env:/app/.env # Store keys persistently
depends_on:
- redis
redis:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes:
- redisdata:/data
volumes:
redisdata:docker-compose up -dThe API & Dashboard will be automatically available at http://localhost:3001.
You don't need to configure secrets manually. Upon the first startup, Jeda detects missing keys, auto-generates them, saves them to .env, and prints them to the terminal:
🔑 API KEY (Kirim Task) : jd_api_7x89ashdas...
🔑 SIGNING KEY (Verifikasi): jd_sig_x834hsdf7sdff...
Every endpoint requires the header: Authorization: Bearer <JEDA_API_KEY>.
Schedule a delayed webhook or an immediate execution.
Request Body:
| Field | Type | Description |
|---|---|---|
destination |
string |
The target URL to hit. |
body |
object |
The JSON payload to send to the destination. |
delay |
string |
(Optional) Time to delay execution. e.g. 30s, 2h, 7d. |
cron |
string |
(Optional) Cron expression. Upgrades task to a periodic schedule. |
timezone |
string |
(Optional) Cron timezone execution. e.g. Asia/Jakarta. |
env |
string |
(Optional) Separates traffic (e.g., production, staging). |
retries |
integer |
(Optional) Override default max retries. |
Advanced Headers:
| Header | Purpose |
|---|---|
Jeda-Forward-[Key] |
Strips the prefix and forwards the header. Example: Jeda-Forward-Authorization: Bearer token. |
Jeda-Deduplication-Id |
Prevents double executions. Exact IDs are deduplicated strictly. |
Jeda-Failure-Callback |
URL notified if the task permanently fails exhaust retries. |
Jeda-Queue-Group |
Enforces Strict FIFO Queues. Tasks with same group ID execute sequentially. |
Retrieve a list of tasks currently scheduled or pending.
Query Parameters:
?queue=(Optional) Filter by queue type (default,scheduler,fifo-*). Default isall.?env=(Optional) Filter by environment metadata.
Modify an existing queued task or cron schedule.
Query Parameters:
?queue=(Required) Specify which queue the task belongs to (e.g.?queue=schedulerfor cron tasks or?queue=defaultfor webhook tasks).
Request Body:
Pass any fields you wish to update (destination, body, cron, delay, env).
Force a queued task or a cron schedule to execute immediately, bypassing the timer. If it's a one-off delayed task, it will be executed and removed from the queue immediately.
Query Parameters:
?queue=(Required) e.g.,?queue=scheduleror?queue=default.
Permamently remove a task/cron schedule from the queue so it no longer triggers.
Query Parameters:
?queue=(Required) Specifying the queue is necessary for Asynq deletion.
Perform an immediate, synchronous HTTP outbound request sent by Jeda to verify URL connectivity without queueing it.
Request Body:
{
"destination": "https://api.example.com",
"body": { "hello": "world" }
}POST /v1/queue/pause: Suspends all worker activity. No webhooks will be fired. Tasks will safely backlog.POST /v1/queue/resume: Resumes processing of backlogged tasks.
How do you know an incoming request is actually from your Jeda instance?
Just like QStash or Stripe, Jeda calculates an HMAC SHA-256 Hash of the exact payload body using your JEDA_SIGNING_KEY. This is attached to every outgoing request in the headers:
Jeda-Signature: t=161110023,v1=a7fb99...Your receiving server simply hashes the raw body with the same signing key to verify authenticity and prevent malicious actors from triggering your endpoints.
Access the beautiful, responsive, and glassmorphism-styled dashboard at /ui.
(Requires you to enter your JEDA_API_KEY for access).
Features:
- 📈 Live Monitoring: Real-time metrics tracking Pending, Active, Success, and Dead-Letter Queue (DLQ) task volumes. No refresh needed (Powered by SSE).
- ✏️ Full Task Management (CRUD):
- Add tasks via beautiful forms with auto-calculating Cron translations (e.g.,
"At 08:00 AM"). - Edit payloads or reschedule pending tasks directly.
- Delete stuck tasks or purge DLQ effortlessly.
- Add tasks via beautiful forms with auto-calculating Cron translations (e.g.,
- ⚡ Fire Test & Force Run: Test webhook endpoints interactively inside the dashboard. Jeda acts like Postman, showing you exactly what HTTP Status and JSON response the target server returns.
- 🛑 Emergency Pause Button: Global kill-switch to pause all outgoing webhooks instantly if your downstream services enter maintenance.
Even as a minimal single-binary app, Jeda enforces strict safety protocols:
- HTTP Client Timeouts: Strict 10-second boundaries. Prevents hanging requests from causing memory leaks.
- Rate Limiting: Protects your Redis instance from infinite-loop DDOS mistakes.
- Dead Letter Queue (DLQ): Tasks that exhaust their Exponential Backoff retries are shelved safely in the DLQ for manual inspection in the Dashboard.
- Graceful Shutdown: Intercepts
SIGTERM/SIGINT. Wait up to 15 seconds for active webhooks to finish flighting before killing the docker container—Zero Ghost Tasks.
MIT License — see LICENSE for details.
Built with ❤️ for hassle-free scheduling.