Self-hosted service
bitleak/lmstfy avatar
bitleak/lmstfy

lmstfy: a small task queue service on Redis with an HTTP API

A task queue with REST API

852 stars122 forksGoMIT

At a glance

What is it?
lmstfy is a Go service that turns a Redis instance into a job queue reachable over HTTP, with publish, consume and delete primitives plus per job TTL, delayed delivery, automatic retries and a dead letter queue. It suits teams that want a queue for a few services rather than a full broker, and it hands durability decisions back to Redis.
Who is it for?
lmstfy suits a team that needs a job queue between a handful of services and would rather speak HTTP than run a broker, especially where a Redis instance already exists and its AOF and replication settings are already trusted for other data.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 178 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A queue with three verbs instead of a broker protocol

lmstfy, short for let me schedule task for you and pronounced /'lam.si.fai/, is described as a simple task queue or job queue service based on Redis storage.

The simplicity is in the protocol. The basic job queue primitives are PUBLISH, CONSUME and DELETE via HTTP API. There is no AMQP handshake, no exchange and binding, no consumer group negotiation. A producer publishes into a namespace and queue, a consumer takes a job with a time to live on the reservation, and the job is acknowledged with a delete.

That is a genuinely different proposition from running a broker. If your services already speak HTTP and already have a Redis deployment, lmstfy adds one small Go service between them rather than a new piece of infrastructure with its own operational model.

The audience is developers building exactly that kind of small background processing. Notifications to send, images to resize, reports to generate, webhooks to fire. The project came out of bitleak, whose own documentation points at a set of client drivers in Go, PHP, Java and Rust, with the Go client described as the most stable and widely used.

The project is MIT licensed and the implementation is Go, with the module built around gin for HTTP, go-redis for storage, viper and cobra for configuration and commands, logrus for logging, prometheus client_golang for metrics, and ULID for job identifiers.

Queue lifecycle features are built into the service

Beyond the three primitives, the README lists extra lifecycle management of jobs: job TTL, a job delay trigger at second granularity, job auto-retry, and a dead letter.

The delay is worth noting for its precision, or rather its lack of it. The README specifies second granularity, which is fine for scheduling work out to minutes or hours but is not a timer with millisecond resolution.

The retry count is set at publish time, and the publish request in the README example creates a job with delay of one second, a TTL of 3600 seconds and tries of 16. Those three numbers are the entire contract with the queue in one URL, which is a neat design: the lifecycle policy travels with the job rather than living in server configuration.

The dead letter is the destination for jobs that exhaust their retries. The README does not describe a re-drive or redrive tool for moving dead letter jobs back into a live queue, and it does not document the threshold at which a job is considered permanently failed. If your workflow depends on inspecting and replaying failed jobs, that is a gap to ask about before you depend on it.

Namespace and queue level metrics are also listed as a feature, and the documentation points to a Grafana dashboard for the project, so the observability side is not an afterthought.

Redis owns the data, so Redis owns the durability

This is the trade-off that matters most in the whole design, and the README states it without hedging. lmstfy itself does not handle data storage, it delegates the storage to Redis or Redis Sentinel currently, with a file based storage backend under implementation. Data integrity and durability is in the hand of Redis, and the project says it uses AOF and replication in its production environment to ensure that.

The practical consequence is that you inherit a durability model you did not choose. Redis with AOF enabled and a replica set gives you a durable log with a single node as the write path, which is a very different set of failure modes from a broker that replicates messages to multiple nodes.

That is a reasonable choice for a queue where losing an in-flight job is tolerable, since the job can be regenerated by the producer, and it is a poor choice for work that must happen exactly once, such as a payment capture.

The file based backend is the interesting piece of information here, and it is also the one to be careful with. The README describes it as under implementing, so it is not a storage option you can plan around today, and the last push to the repository was on 2026-04-29, with the same date carrying the v1.0.16 release tag.

Namespaces, tokens and two listening ports

The service listens on two ports, and the README states that the server listens on localhost:7777 with the admin port on localhost:7778.

The split is functional. The first port carries the job API for publishers and consumers. The second carries administration, and it is where a namespace and its token are created, with a POST to the token path of the admin port.

shell
 % curl -XPOST -d "description=test namesapce" "http://127.0.0.1:7778/token/test-ns" 

The token then authenticates job operations, passed in the X-token header on every request. Namespaces are the tenant boundary, and the token belongs to one.

That structure explains the rate limiting feature, which is described as a token consume and produce rate limit. Limits are enforced per token rather than per end user, so the granularity of your throttling is the granularity of your token issuance. If you give every producer its own token you can throttle finely, and if you share one token across a fleet you have throttled the fleet as a unit. The README does not document the algorithm or the default limits, and points to a separate Throttler API document, with English and Chinese versions, for that.

The token in the example above is a placeholder, so treat any request you copy as incomplete until you substitute a real one.

Publishing and consuming is four HTTP calls

The job API is small enough to read in one sitting, and the README walks through it with curl commands.

Publishing puts a job into a namespace and queue. The queue does not need to exist in advance, since the comment in the README notes that the queue would be dynamically created, so you can publish to any queue.

shell
# The queue would be dynamic created, so feel free to publish the message to any queues.
# Below http request would create a job with delay = 1s, ttl = 3600s and tries = 16.

 % curl -XPUT -H "X-token:{ENTER YOUR TOKEN}" "http://127.0.0.1:7777/api/test-ns/test-queue?delay=1&ttl=3600&tries=16" 

Consuming takes a job off the queue, and the two query parameters control the reservation and the wait. The README example requests a time to reserve of 30 seconds and a timeout of 2 seconds, which is the shape of a long poll: ask, wait briefly, get a job if one appears.

shell
 % curl -H "X-token:{ENTER YOUR TOKEN}" "http://127.0.0.1:7777/api/test-ns/test-queue?ttr=30&timeout=2" 

Acknowledging is a delete against the job identifier, which the README shows with a placeholder for the job ID.

shell
 % curl -i -XDELETE -H "X-token:{ENTER YOUR TOKEN}" "http://127.0.0.1:7777/api/test-ns/test-queue/job/{YOUR JOB ID}" 

So the lifecycle is: publish, reserve with a time to reserve, do the work, delete to acknowledge. If a consumer dies without acknowledging, the reservation expires and the retry policy takes over, which is the standard mechanism for at least once delivery.

docker-compose is for trying it, not for running it

The README is unusually direct about this. If you just want to have a try, the docker-compose is highly recommended but do not use it in production. The stated purpose is to set up and play with lmstfy, running the Redis and lmstfy server.

shell
# run the lmstfy and redis, server would listen on localhost:7777
# and admin port on localhost:7778.

 % cd docker && docker-compose -p test-lmstfy up -d

The project name in the compose command is test-lmstfy, which is consistent with it being a scratch setup rather than a deployment.

For real deployments the README points at the configuration files. The service is started with the server binary and a configuration path, and the example given is the demo configuration.

shell
_build/lmstfy-server -c config/demo-conf.toml

Configuration is TOML, read through viper, and the repository has a config directory with several variants, including the one referenced by the container image.

The container image itself shows the production shape. It builds in a golang:1.17 builder stage, runs make, and copies the result into an ubuntu:20.04 image that exposes the API port and points its entrypoint at the Docker specific configuration.

dockerfile
FROM golang:1.17 AS builder
WORKDIR /lmstfy
ADD ./ /lmstfy
RUN apt update -y && apt install -y netcat
RUN cd /lmstfy && make

One practical warning for anyone planning to run this in production: the Go toolchain in the build is 1.17, and the module declares go 1.17. That is an old baseline, and it is the kind of detail that matters when you have to rebuild the binary from source for a new architecture.

Building from source, and the client drivers around it

Building is described as simple as make, and the binary is produced in the _build directory, from where it is run with the config file as shown above.

shell
% make

The Makefile carries the usual supporting targets. There is a setup target that runs scripts/setup.sh and a teardown target for scripts/teardown.sh, which look like they manage the local Redis used for development. A test target runs scripts/run-test.sh, and a lint target checks formatting with gofmt and runs go vet across the packages. The build itself is delegated to build.sh.

The client side is where the multi language story shows. The README lists drivers for Go, PHP, Java and Rust, each in its own repository, with the Go client marked as the most stable and widely used. That list matters when you are choosing this queue: your producers and consumers are probably not all Go, and having a maintained client for each language removes a common reason to pick something else.

The documentation set is split by audience. There is an HTTP API document for the job interface, an administration API document in English and Chinese for namespace and token management, and a Throttler API document in English and Chinese for the rate limiting, plus a Grafana dashboard link for metrics.

The repository layout shows the separation of concerns behind the design: an engine directory for the queue logic, a throttler directory for rate limiting, an auth directory for token handling, a server directory for the HTTP layer, a client directory for the Go client, and helper, log, uuid and version packages.

Editorial conclusion

lmstfy suits a team that needs a job queue between a handful of services and would rather speak HTTP than run a broker, especially where a Redis instance already exists and its AOF and replication settings are already trusted for other data. It does not suit workloads that need message routing, priority or exactly once semantics beyond what a single Redis instance provides, and it does not suit you if you are unwilling to own the durability of the underlying store, because the README is explicit that data integrity and durability are in the hand of Redis. Before adopting it, decide whether you want Redis Sentinel or a single instance, confirm the file based storage backend is still only under implementation before you design around it, and read the token and throttler documentation, since the rate limits are enforced per token rather than per user.

Frequently asked questions

What is lmstfy and what is it built on?

lmstfy is a simple task queue or job queue service based on Redis storage, written in Go. It exposes basic job queue primitives, PUBLISH, CONSUME and DELETE, through an HTTP API.

How do I run lmstfy locally to try it?

The README recommends docker-compose for trying it out and warns not to use it in production. Run cd docker && docker-compose -p test-lmstfy up -d, which starts Redis and lmstfy with the API on localhost:7777 and the admin port on localhost:7778.

Where does lmstfy store the jobs?

lmstfy does not handle data storage itself and delegates it to Redis or Redis Sentinel, with a file based storage backend under implementation. The README says data integrity and durability are in the hand of Redis, and that AOF and replication are used in its production environment.

How do I authenticate to the job API?

You create a namespace and token through the admin port, for example a POST to http://127.0.0.1:7778/token/test-ns, and then pass the token in the X-token header on every job request. Rate limits are applied per token for consume and produce operations.

Which job lifecycle features does lmstfy support?

The README lists job TTL, a job delay trigger at second granularity, job auto-retry and a dead letter, plus namespace and queue level metrics. The publish request in the README sets delay, ttl and tries in the query string.

Official sources

  1. bitleak/lmstfy on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/bitleak-lmstfy.svg)](https://hysenlabs.com/projects/bitleak-lmstfy)

Was this useful?