Reliable messaging, written from scratch.
DSend is a lightweight message broker built in Go — no Kafka, no RabbitMQ, no dependencies. Publish, consume, and acknowledge messages with durable storage, retries, and dead-lettering, all over a plain TCP/JSON protocol.
# terminal 1 — start the broker
$ go build -o dsend ./cmd/dsend
$ ./dsend server
[server] listening on 127.0.0.1:8080
[wal] appending to ./data/wal.log
# terminal 2 — publish a message
# terminal 3 — subscribe and receive it
$ ./dsend publish --queue orders "order-1042 created"
Message Sent successfully
$ ./dsend subscribe --queue orders
Received message: order-1042 created
Everything a broker needs. Nothing it doesn't.
Six core building blocks, implemented from scratch and exposed through a small, typed Go SDK.
Named queues
Isolated in-memory ring buffers. Publish to any queue name, or the default queue when none is given.
At-least-once delivery
Delivered messages are tracked in flight until a consumer acknowledges them. Nothing is silently lost.
ACK + redelivery
Messages that aren't acknowledged within the timeout are redelivered, up to a configured retry limit.
Dead-letter queue
Messages that exhaust their retries move to a per-queue DLQ instead of vanishing into the void.
Durable write-ahead log
Every publish, ack, and requeue is appended to a WAL. Restart the broker and the queues come back.
Runtime metrics
Produced, acked, in-flight, redelivered, DLQ depth, and consumer sessions — per queue or broker-wide.
Producers push. Consumers pull with a broker in between.
One TCP connection per client, JSON on the wire, and a broker core that schedules delivery round-robin across consumers.
Producer
client.NewProducer
- Publish
- Metrics
- Queue admin
Broker
per-queue runtime
- ring buffer
- in-flight
- DLQ
- WAL
Consumer
client.NewConsumer
- Subscribe
- Receive
- Ack
↩
Consumers return an ack token after processing; the broker removes the message from its in-flight
registry. Unacked messages are redelivered ack-timeout later.
A producer and a consumer, in two files.
The SDK is a single Go package: github.com/Ali-Hasan-Khan/dsend/client.
package main
import (
"context"
"fmt"
"github.com/Ali-Hasan-Khan/dsend/client"
)
func main() {
p, err := client.NewProducer("localhost:8080")
if err != nil {
panic(err)
}
defer p.Close()
err = p.Publish(context.Background(),
"orders", "order-1042 created")
if err != nil {
panic(err)
}
fmt.Println("published")
}
package main
import (
"context"
"fmt"
"github.com/Ali-Hasan-Khan/dsend/client"
)
func main() {
c, err := client.NewConsumer("localhost:8080")
if err != nil {
panic(err)
}
defer c.Close()
if err := c.Subscribe("orders"); err != nil {
panic(err)
}
for {
msg, err := c.Receive(context.Background())
if err != nil {
panic(err)
}
fmt.Println("got:", msg.Payload)
c.Ack(msg.AckToken) // mark as processed
}
}
Start the broker
Build and run the server. It listens on 127.0.0.1:8080 and persists to ./data/wal.log.
Run the producer
Open a connection and call Publish. The message is appended to the WAL before it's queued.
Consume & ack
Subscribe, receive deliveries, and call Ack after processing. Failures are redelivered automatically.
See what the broker is doing, at a glance.
Query broker-wide or per-queue metrics through the SDK or the CLI. Every counter tracks a concrete delivery event.
- ProducedCount
- messages appended to the WAL and queued
- AckedCount
- messages acknowledged by consumers
- QueueDepth / InflightCount
- waiting in the ring buffer vs. reserved by a consumer
- RedeliveredCount / DlqCount
- requeued after ack timeout vs. dead-lettered after retries
Queue: orders
ProducedCount: 10
QueueDepth: 0
InflightCount: 0
DlqCount: 0
ConsumerSessionCount: 1
AckedCount: 10
RedeliveredCount: 0
Ready to move messages between Go services?
Read the SDK docs and get your first producer running in five minutes.