<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Augustus</title>
    <description>The latest articles on DEV Community by Augustus (@augiwan).</description>
    <link>https://dev.to/augiwan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1687401%2F29cb38de-45b1-4766-897b-d8c9fdc85a32.jpeg</url>
      <title>DEV Community: Augustus</title>
      <link>https://dev.to/augiwan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/augiwan"/>
    <language>en</language>
    <item>
      <title>I open-sourced our Redis-based webhook replacement (And why you should try it)</title>
      <dc:creator>Augustus</dc:creator>
      <pubDate>Mon, 17 Mar 2025 01:52:05 +0000</pubDate>
      <link>https://dev.to/augiwan/i-open-sourced-our-redis-based-webhook-replacement-and-why-you-should-try-it-1lo3</link>
      <guid>https://dev.to/augiwan/i-open-sourced-our-redis-based-webhook-replacement-and-why-you-should-try-it-1lo3</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Built a lightweight Redis-based message queue that replaces unreliable HTTP webhooks while maintaining a familiar developer experience (looks and feels almost exactly like POST requests)&lt;/li&gt;
&lt;li&gt;Solves common webhook issues: service downtime, retry complexity, rate limiting, and lack of transactions.&lt;/li&gt;
&lt;li&gt;Provides dead letter queues, transactions, TTL, and more.&lt;/li&gt;
&lt;li&gt;Perfect for teams scaling microservices who want reliability without the operational complexity of Kafka/RabbitMQ&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://leanmq.augiwan.com/" rel="noopener noreferrer"&gt;Open source&lt;/a&gt; and &lt;a href="https://github.com/augiwan/LeanMQ" rel="noopener noreferrer"&gt;available on GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Every developer who has built microservices at scale knows the struggle. You start with a simple architecture and basic HTTP webhooks between services. Everything works great—until it doesn't.&lt;/p&gt;

&lt;p&gt;Messages get lost when services go down. Rate limiting causes cascading failures. Retries become a tangled mess. And soon enough, you're debugging outages instead of shipping features.&lt;/p&gt;

&lt;p&gt;That's exactly where we found ourselves last year—and why I built LeanMQ, an internal tool that has now become my newest open-source project.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Internal Webhooks
&lt;/h2&gt;

&lt;p&gt;I've worked with various messaging patterns over the years. When we started with a new project, we initially chose simple HTTP webhooks for service-to-service communication. This approach is common and works well at certain scales:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Service A sends a webhook
&lt;/span&gt;&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://service-b/webhook/order-status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shipped&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;# Service B receives it
&lt;/span&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/webhook/order-status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_order_status&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;
    &lt;span class="c1"&gt;# Process the webhook...
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If anything fails when sending, then add it to a queue in Postgres and re-attempt in a CRON.&lt;/p&gt;

&lt;p&gt;But as we scaled to millions of webhooks per month, several architectural limitations became apparent:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Service availability coupling&lt;/strong&gt;: When a receiving service was down, we needed complex retry mechanisms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complex retry logic&lt;/strong&gt;: Each service reimplemented similar retry patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting challenges&lt;/strong&gt;: Services under heavy load would reject webhooks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debugging complexity&lt;/strong&gt;: Limited visibility into webhook delivery status&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lack of transactional guarantees&lt;/strong&gt;: Difficult to ensure multiple services were updated atomically&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We thoroughly evaluated several alternatives:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cloud-based webhook services&lt;/strong&gt;: We tested services like Svix, Hookdeck, and others, but the volume of our webhooks (millions per month with plans to scale to hundreds of millions) made the pricing prohibitively expensive, which would lead to a terrible ROI for our specific use case.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enterprise message brokers&lt;/strong&gt;: We looked at RabbitMQ, Kafka, and other established solutions. While these are excellent products with rich feature sets, they introduced significant operational complexity, required specialized knowledge, and would have necessitated substantial architectural changes.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Our services were already using Redis extensively, so we wanted to leverage our existing infrastructure if possible. We needed something that maintained the familiar webhook pattern but provided the reliability of a message queue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Internal Solution
&lt;/h2&gt;

&lt;p&gt;After evaluating the tradeoffs, I realized we could build a solution that combined the best of both worlds: the simplicity and familiar developer experience of webhooks with the reliability of a proper message queue, all while leveraging our existing Redis infrastructure.&lt;/p&gt;

&lt;p&gt;Redis Streams — a relatively new feature in Redis — provided the perfect foundation. It offered persistence, consumer groups, and powerful primitives for building reliable message delivery without adding new infrastructure components to our stack.&lt;/p&gt;

&lt;p&gt;I designed an abstraction layer around Redis Streams with two key design principles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Maintain the familiar webhook-like developer experience&lt;/li&gt;
&lt;li&gt;Add production-grade reliability features like DLQs and transactions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result was a simple API that required minimal changes to existing code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Replace this:
&lt;/span&gt;&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://service-b/webhook/order-status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# With this:
&lt;/span&gt;&lt;span class="n"&gt;webhook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/order/status/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the receiving end, the API closely resembled web frameworks everyone is already familiar with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Instead of a Flask or FastAPI route, use this decorator:
&lt;/span&gt;&lt;span class="nd"&gt;@webhook.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/order/status/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_order_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Process the webhook data...
&lt;/span&gt;    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="c1"&gt;# And run a service to process incoming webhooks
&lt;/span&gt;&lt;span class="n"&gt;webhook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_service&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Smaller projects can skip the service and
# simply run this in a CRON script
&lt;/span&gt;&lt;span class="n"&gt;webhook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process_messages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One of the biggest advantages was the introduction of transactions. We could now atomically send multiple messages to different services, ensuring that either all operations succeeded or none did—something that was nearly impossible with our previous HTTP webhook approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Either both messages are delivered or neither is
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;webhook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/order/status/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shipped&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/inventory/update/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;product_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;456&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quantity_change&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within weeks, our webhook-related challenges were addressed. Failed messages went to dead letter queues for easy inspection and reprocessing. Services could go down without affecting the reliability of our messaging. We gained atomic transactions for operations that needed to span multiple services.&lt;/p&gt;

&lt;p&gt;And the best part? The development experience remained nearly identical to our previous webhook pattern, making adoption painless across the engineering team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Open Source?
&lt;/h2&gt;

&lt;p&gt;I liked how this library solved our internal async problems and I thought maybe it could help other teams facing similar challenges. I spent evenings and weekends polishing the codebase, adding documentation, and preparing it for public release as &lt;a href="https://github.com/augiwan/LeanMQ" rel="noopener noreferrer"&gt;LeanMQ&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As with any project, the journey from an internal tool to an open-source project taught me a few things, especially developer experience and documentation...&lt;/p&gt;

&lt;h2&gt;
  
  
  A few lessons
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Documentation is (almost) everything
&lt;/h3&gt;

&lt;p&gt;Documentation can make or break an open-source project. I spent more time on docs than on the actual code! I aimed to create documentation that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Makes it really easy to get started&lt;/li&gt;
&lt;li&gt;But can go deep when developers want to explore more&lt;/li&gt;
&lt;li&gt;Provides real-world examples&lt;/li&gt;
&lt;li&gt;Addresses common scenarios&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This led to a comprehensive &lt;a href="http://leanmq.augiwan.com/" rel="noopener noreferrer"&gt;documentation site&lt;/a&gt; with detailed guides, examples, and reference materials.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. API design is a delicate balance
&lt;/h3&gt;

&lt;p&gt;When designing APIs for others to use, there's a constant tension between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simplicity vs. flexibility&lt;/li&gt;
&lt;li&gt;Convention vs. configuration&lt;/li&gt;
&lt;li&gt;Opinionated vs. unopinionated design&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I ultimately opted for a simple, opinionated core API with escape hatches for advanced use cases. This made the library approachable while still supporting complex scenarios.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Community starts before your first user
&lt;/h3&gt;

&lt;p&gt;It would be awesome if we do build an open-source community around this library. Maybe there are more niche use cases we can cover. And even if a few developers use it, I want it to delight!&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Deep Dive: How LeanMQ Works
&lt;/h2&gt;

&lt;p&gt;For those interested in the technical details, LeanMQ has several key components:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Core Message Queue (simple but powerful)
&lt;/h3&gt;

&lt;p&gt;At its heart, LeanMQ provides a simple but powerful message queue abstraction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;leanmq&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LeanMQ&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize the client
&lt;/span&gt;&lt;span class="n"&gt;mq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LeanMQ&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redis_host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;redis_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6379&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Create queues
&lt;/span&gt;&lt;span class="n"&gt;main_queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dlq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_queue_pair&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;notifications&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Send a message
&lt;/span&gt;&lt;span class="n"&gt;message_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;main_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_message&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;# Get and process messages
&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;main_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_messages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Process the message...
&lt;/span&gt;    &lt;span class="n"&gt;main_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;acknowledge_messages&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. The Webhook Pattern (the main use-case of LeanMQ)
&lt;/h3&gt;

&lt;p&gt;On top of this core, LeanMQ adds a webhook-like pattern for easier service-to-service communication:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;leanmq&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LeanMQWebhook&lt;/span&gt;

&lt;span class="n"&gt;webhook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LeanMQWebhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redis_host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;redis_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6379&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Register handlers with a familiar decorator pattern
&lt;/span&gt;&lt;span class="nd"&gt;@webhook.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/order/status/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_order_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Order &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; status: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Run a service to process messages
&lt;/span&gt;&lt;span class="n"&gt;webhook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_service&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Advanced Features
&lt;/h3&gt;

&lt;p&gt;LeanMQ includes several advanced features common in enterprise message queues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transactions&lt;/strong&gt;: Send multiple messages atomically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Message TTL&lt;/strong&gt;: Automatically expire old messages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dead Letter Queues&lt;/strong&gt;: Capture and inspect failed messages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consumer Groups&lt;/strong&gt;: Distribute processing across workers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Transactions were particularly important for our use case. In a distributed system, ensuring consistent state across multiple services is challenging. For example, when an order is shipped, we might need to update the order status, decrement inventory, and notify the customer—all as an atomic operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Atomic transactions ensure all messages are sent or none are
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;mq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orders_queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shipped&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inventory_queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;product_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ABC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quantity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;notifications_queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;456&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_shipped&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With traditional webhooks, implementing this pattern reliably would require complex distributed transaction patterns or eventual consistency mechanisms. LeanMQ makes it trivial while maintaining the familiar webhook-like developer experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Road Ahead
&lt;/h2&gt;

&lt;p&gt;Open-sourcing LeanMQ is just the beginning. I have plans to add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More language bindings (Node.js, Go)&lt;/li&gt;
&lt;li&gt;Additional transport options beyond Redis&lt;/li&gt;
&lt;li&gt;Enhanced monitoring and observability&lt;/li&gt;
&lt;li&gt;Performance optimizations for high-throughput scenarios&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm not trying to replace established message brokers like RabbitMQ or Kafka—they're excellent solutions for many use cases. LeanMQ fills a specific niche: providing reliable asynchronous communication with minimal operational overhead and a webhook-like developer experience.&lt;/p&gt;

&lt;p&gt;I'm looking forward to seeing how others use, extend, and improve upon this foundation, especially teams that want to upgrade their internal webhooks without adopting a full-scale message broker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Out
&lt;/h2&gt;

&lt;p&gt;If you're dealing with internal webhooks or seeking a lightweight message queue solution, give LeanMQ a try:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;leanmq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check out the &lt;a href="http://leanmq.augiwan.com/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; and &lt;a href="https://github.com/augiwan/LeanMQ" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I'd love to hear your feedback, questions, and suggestions in the comments.&lt;/p&gt;




&lt;p&gt;Have you open-sourced an internal tool? What challenges did you face? What do you think of LeanMQ?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>microservices</category>
      <category>eventdriven</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
