<?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>Forem: Md Zonieed Hossain</title>
    <description>The latest articles on Forem by Md Zonieed Hossain (@md_zonieedhossain_d19709).</description>
    <link>https://forem.com/md_zonieedhossain_d19709</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%2F3855776%2F1dce5cc5-7abe-4863-a94b-ccb2438013b3.jpg</url>
      <title>Forem: Md Zonieed Hossain</title>
      <link>https://forem.com/md_zonieedhossain_d19709</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/md_zonieedhossain_d19709"/>
    <language>en</language>
    <item>
      <title>Kafka Inbox/Outbox in Go — Guaranteeing Exactly-Once Delivery in a Multi-Tenant ERP</title>
      <dc:creator>Md Zonieed Hossain</dc:creator>
      <pubDate>Wed, 01 Apr 2026 14:00:54 +0000</pubDate>
      <link>https://forem.com/md_zonieedhossain_d19709/kafka-inboxoutbox-in-go-guaranteeing-exactly-once-delivery-in-a-multi-tenant-erp-320k</link>
      <guid>https://forem.com/md_zonieedhossain_d19709/kafka-inboxoutbox-in-go-guaranteeing-exactly-once-delivery-in-a-multi-tenant-erp-320k</guid>
      <description>&lt;h1&gt;
  
  
  Kafka Inbox/Outbox in Go — Guaranteeing Exactly-Once Delivery in a Multi-Tenant ERP
&lt;/h1&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;In a multi-tenant ERP handling concurrent stock and order updates, &lt;br&gt;
duplicate event processing is not a theoretical risk — it is a &lt;br&gt;
production reality.&lt;/p&gt;

&lt;p&gt;In our system at Gononet, duplicate events happened for two reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Network retries&lt;/strong&gt; — a message is delivered but the consumer 
crashes before committing the offset. Kafka re-delivers it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Double-submit orders&lt;/strong&gt; — a user clicks "confirm order" twice 
due to slow network. Two events hit the queue simultaneously.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result? Stock deducted twice. Orders created twice. &lt;br&gt;
Inventory counts wrong. In a warehouse system, this is a critical failure.&lt;/p&gt;

&lt;p&gt;We needed exactly-once delivery. This is how we built it.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why Not Just Use Kafka Transactions?
&lt;/h2&gt;

&lt;p&gt;Kafka transactions guarantee exactly-once delivery &lt;br&gt;
&lt;strong&gt;within Kafka itself&lt;/strong&gt; — producer to broker. But our problem was &lt;br&gt;
on the consumer side. We needed to guarantee that our &lt;br&gt;
&lt;strong&gt;business logic&lt;/strong&gt; — stock deduction, ledger insert — &lt;br&gt;
executed exactly once, not just that the message arrived once.&lt;/p&gt;

&lt;p&gt;Kafka transactions do not solve this. The Inbox pattern does.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Inbox Pattern — Core Idea
&lt;/h2&gt;

&lt;p&gt;The idea is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When a message arrives, &lt;strong&gt;first record it&lt;/strong&gt; in an &lt;code&gt;inbox&lt;/code&gt; table 
with its unique &lt;code&gt;message_id&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Wrap the &lt;strong&gt;inbox insert + business logic + ledger update&lt;/strong&gt; 
in a single database transaction&lt;/li&gt;
&lt;li&gt;If the &lt;code&gt;message_id&lt;/code&gt; already exists — the transaction was 
already processed. Return early.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because everything happens in one atomic transaction, &lt;br&gt;
there is no window for duplicates.&lt;/p&gt;


&lt;h2&gt;
  
  
  Implementation in Go
&lt;/h2&gt;
&lt;h3&gt;
  
  
  The Inbox Table
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;inventory_inbox&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;message_id&lt;/span&gt;   &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;processed_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Simple. The &lt;code&gt;message_id&lt;/code&gt; is the Kafka message key — &lt;br&gt;
unique per business event.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Repository
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// inbox.go&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;InsertFirstAttempt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="n"&gt;bun&lt;/span&gt;&lt;span class="o"&gt;.&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;messageID&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewInsert&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
        &lt;span class="n"&gt;TableExpr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"inventory_inbox"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
        &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"message_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;messageID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
        &lt;span class="n"&gt;On&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CONFLICT (message_id) DO NOTHING"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
        &lt;span class="n"&gt;Exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RowsAffected&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If &lt;code&gt;rows == 0&lt;/code&gt; — the message was already processed. &lt;br&gt;
We return early. No duplicate processing.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Consumer
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Consumer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;HandleStockUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="n"&gt;kafka&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&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="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BeginTx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Rollback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Step 1 — check inbox&lt;/span&gt;
    &lt;span class="n"&gt;isFirst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InsertFirstAttempt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ctx&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;isFirst&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// duplicate — skip silently&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Step 2 — business logic inside same transaction&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DeductStock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&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="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Step 3 — commit everything atomically&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The inbox check, stock deduction and commit happen &lt;br&gt;
in &lt;strong&gt;one transaction&lt;/strong&gt;. Either all succeed or none do.&lt;/p&gt;


&lt;h2&gt;
  
  
  Handling the Offset Commit Failure Edge Case
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: What if the transaction commits but Kafka offset commit fails?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Kafka will re-deliver the message. But when it arrives again, &lt;br&gt;
&lt;code&gt;InsertFirstAttempt&lt;/code&gt; finds the &lt;code&gt;message_id&lt;/code&gt; already in the inbox &lt;br&gt;
and returns early. The business logic never runs twice.&lt;/p&gt;

&lt;p&gt;This is the key insight — the inbox makes the consumer &lt;br&gt;
&lt;strong&gt;idempotent by design&lt;/strong&gt;, not by luck.&lt;/p&gt;


&lt;h2&gt;
  
  
  Inbox Table Retention
&lt;/h2&gt;

&lt;p&gt;The inbox table cannot grow forever. We run a background &lt;br&gt;
cleanup worker using a cron job that deletes records older &lt;br&gt;
than 7 days:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;CleanInbox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewDelete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
        &lt;span class="n"&gt;TableExpr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"inventory_inbox"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
        &lt;span class="n"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"processed_at &amp;lt; NOW() - INTERVAL '7 days'"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
        &lt;span class="n"&gt;Exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;7 days is a safe buffer — duplicates in practice arrive &lt;br&gt;
within seconds or minutes, never days.&lt;/p&gt;




&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;After implementing the Kafka Inbox pattern across our &lt;br&gt;
stock and order workflows at Gononet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Duplicate stock deductions caused by network retries — &lt;strong&gt;eliminated&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Double-submit order duplicates — &lt;strong&gt;eliminated&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Zero changes required to Kafka configuration or producer code&lt;/li&gt;
&lt;li&gt;Pattern reusable across any consumer in the system&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  When To Use This Pattern
&lt;/h2&gt;

&lt;p&gt;Use the Inbox pattern when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have at-least-once Kafka delivery (the default)&lt;/li&gt;
&lt;li&gt;Your consumer does database writes as part of business logic&lt;/li&gt;
&lt;li&gt;Duplicate processing has real business consequences 
(money, inventory, orders)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not use it when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your consumer is read-only&lt;/li&gt;
&lt;li&gt;Duplicate processing is harmless (analytics, logging)&lt;/li&gt;
&lt;li&gt;You can use Kafka Streams with exactly-once semantics natively&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The Kafka Inbox pattern is one of the most practical tools &lt;br&gt;
in distributed systems engineering. It does not require &lt;br&gt;
exotic infrastructure — just a table, a transaction and &lt;br&gt;
a unique message ID.&lt;/p&gt;

&lt;p&gt;If you are building event-driven systems where correctness &lt;br&gt;
matters more than throughput, this pattern belongs in your toolkit.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I am a Senior Backend Engineer specialising in Go distributed &lt;br&gt;
systems. Currently building RetailerBook ERP and GorillaMove &lt;br&gt;
grocery delivery backend at Gononet. Open to senior backend &lt;br&gt;
and staff engineer roles — remote or relocation worldwide.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;GitHub: github.com/zonieedhossain&lt;/em&gt;&lt;br&gt;
&lt;em&gt;LinkedIn: linkedin.com/in/zonieedhossain&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>kafka</category>
      <category>distributedsystems</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
