<?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: namastack.io</title>
    <description>The latest articles on Forem by namastack.io (@namastack).</description>
    <link>https://forem.com/namastack</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%2Forganization%2Fprofile_image%2F11907%2Feea5f5ea-b672-4aa4-9ef6-72bd4efdb2d7.png</url>
      <title>Forem: namastack.io</title>
      <link>https://forem.com/namastack</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/namastack"/>
    <language>en</language>
    <item>
      <title>Outbox Pattern: The Hard Parts (and How Namastack Outbox Helps)</title>
      <dc:creator>Roland Beisel</dc:creator>
      <pubDate>Mon, 19 Jan 2026 19:51:30 +0000</pubDate>
      <link>https://forem.com/namastack/outbox-pattern-the-hard-parts-and-how-namastack-outbox-helps-507m</link>
      <guid>https://forem.com/namastack/outbox-pattern-the-hard-parts-and-how-namastack-outbox-helps-507m</guid>
      <description>&lt;p&gt;Most people know the Transactional Outbox Pattern at a high level.&lt;/p&gt;

&lt;p&gt;What’s often missing are the production-grade details - the “hard parts” that decide whether your outbox is reliable under real load and failures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ordering semantics (usually per aggregate/key, not global) and what happens when one record in a sequence fails&lt;/li&gt;
&lt;li&gt;scaling across multiple instances without lock pain (partitioning + rebalancing)&lt;/li&gt;
&lt;li&gt;retries that behave well during outages&lt;/li&gt;
&lt;li&gt;a clear strategy for permanently failed records&lt;/li&gt;
&lt;li&gt;monitoring and operations (backlog, failures, partitions, cluster health)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article focuses on those hard parts and how &lt;a href="https://outbox.namastack.io/" rel="noopener noreferrer"&gt;&lt;strong&gt;Namastack Outbox&lt;/strong&gt;&lt;/a&gt; addresses them.&lt;/p&gt;

&lt;p&gt;If you want to browse the relevant docs sections upfront:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://outbox.namastack.io/latest/features/core/#record-ordering" rel="noopener noreferrer"&gt;Ordering &amp;amp; partitioning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://outbox.namastack.io/latest/features/database/#schema-management" rel="noopener noreferrer"&gt;Schema management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://outbox.namastack.io/latest/features/monitoring/" rel="noopener noreferrer"&gt;Monitoring &amp;amp; metrics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://outbox.namastack.io/latest/features/configuration/" rel="noopener noreferrer"&gt;Configuration reference&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want a quick primer first, this video introduces &lt;strong&gt;Namastack Outbox&lt;/strong&gt; and recaps the basic concepts behind the Outbox Pattern:&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/857Pm6iC1Fw"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hard Parts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Ordering: what you actually need in production
&lt;/h3&gt;

&lt;p&gt;When people say “we need ordering”, they often mean &lt;em&gt;global ordering&lt;/em&gt;. In production, that’s usually the wrong goal.&lt;/p&gt;

&lt;p&gt;What you typically need is &lt;strong&gt;ordering per business key&lt;/strong&gt; (often per aggregate):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;for a given &lt;code&gt;order-123&lt;/code&gt;, process records strictly &lt;strong&gt;in creation order&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;for different keys (&lt;code&gt;order-456&lt;/code&gt;, &lt;code&gt;order-789&lt;/code&gt;), process in parallel&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  How Namastack Outbox defines ordering
&lt;/h4&gt;

&lt;p&gt;Ordering is defined by the &lt;strong&gt;record key&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;same key → sequential, deterministic processing&lt;/li&gt;
&lt;li&gt;different keys → concurrent processing
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;outbox&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Outbox&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;orderRepository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;OrderRepository&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Transactional&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;createOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderCommand&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;order&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;orderRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;// Schedule event - saved atomically with the order&lt;/span&gt;
        &lt;span class="n"&gt;outbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OrderCreatedEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"order-${order.id}"&lt;/span&gt;  &lt;span class="c1"&gt;// Groups records for ordered processing&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Spring events:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@OutboxEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"#this.orderId"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;OrderCreatedEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Failure behavior: should later records wait?
&lt;/h4&gt;

&lt;p&gt;The key production question is what happens if one record in the sequence fails.&lt;/p&gt;

&lt;p&gt;By default (&lt;code&gt;outbox.processing.stop-on-first-failure=true&lt;/code&gt;), later records with the same key &lt;strong&gt;wait&lt;/strong&gt;. This preserves strict semantics when records depend on each other.&lt;/p&gt;

&lt;p&gt;If records are independent, you can set &lt;code&gt;outbox.processing.stop-on-first-failure=false&lt;/code&gt; so failures don’t block later records for the same key.&lt;/p&gt;

&lt;h4&gt;
  
  
  Choosing good keys
&lt;/h4&gt;

&lt;p&gt;Use the key for the &lt;strong&gt;unit where ordering matters&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;order-${orderId}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;customer-${customerId}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid keys that are too coarse (serialize everything), e.g. &lt;code&gt;"global"&lt;/code&gt;.&lt;br&gt;
Avoid keys that are too fine (no ordering), e.g. a random UUID.&lt;/p&gt;
&lt;h4&gt;
  
  
  Why ordering still works when scaling out
&lt;/h4&gt;

&lt;p&gt;Namastack Outbox combines key-based ordering with &lt;strong&gt;hash-based partitioning&lt;/strong&gt;, so a key consistently routes to the same partition and one active instance processes it at a time.&lt;/p&gt;


&lt;h3&gt;
  
  
  Scaling: partitioning and rebalancing
&lt;/h3&gt;

&lt;p&gt;Scaling an outbox from 1 instance to N instances is where many implementations fall apart.&lt;/p&gt;

&lt;p&gt;At that point you need two things at the same time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;work distribution&lt;/strong&gt; (so all instances can help)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;no double processing + preserved ordering&lt;/strong&gt; (especially for the same key)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A common approach is “just use database locks”. That can work, but it often brings lock contention, hot rows, and unpredictable latency once traffic grows.&lt;/p&gt;
&lt;h4&gt;
  
  
  Namastack Outbox approach: hash-based partitioning
&lt;/h4&gt;

&lt;p&gt;Instead of distributed locking, Namastack Outbox uses &lt;strong&gt;hash-based partitioning&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There are &lt;strong&gt;256 fixed partitions&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Each record key is mapped to a partition using consistent hashing.&lt;/li&gt;
&lt;li&gt;Each application instance owns a subset of those partitions.&lt;/li&gt;
&lt;li&gt;An instance only polls/processes records for its assigned partitions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Different instances don’t compete for the same records (low lock contention).&lt;/li&gt;
&lt;li&gt;Ordering stays meaningful: &lt;strong&gt;same key → same partition → processed sequentially&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  What rebalancing means
&lt;/h4&gt;

&lt;p&gt;In production, the number of active instances changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you deploy a new version (rolling restart)&lt;/li&gt;
&lt;li&gt;autoscaling adds/removes pods&lt;/li&gt;
&lt;li&gt;an instance crashes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Namastack Outbox periodically re-evaluates which instances are alive and redistributes partitions.&lt;br&gt;
This is the “rebalancing” part.&lt;/p&gt;

&lt;p&gt;Important: rebalancing is designed to be automatic — you shouldn’t need a separate coordinator.&lt;/p&gt;
&lt;h4&gt;
  
  
  Instance coordination knobs
&lt;/h4&gt;

&lt;p&gt;These settings control how instances coordinate and detect failures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;outbox&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;rebalance-interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10000&lt;/span&gt;                  &lt;span class="c1"&gt;# ms between rebalance checks&lt;/span&gt;

  &lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;heartbeat-interval-seconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;            &lt;span class="c1"&gt;# how often to send heartbeats&lt;/span&gt;
    &lt;span class="na"&gt;stale-instance-timeout-seconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;       &lt;span class="c1"&gt;# when to consider an instance dead&lt;/span&gt;
    &lt;span class="na"&gt;graceful-shutdown-timeout-seconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;    &lt;span class="c1"&gt;# time to hand over partitions on shutdown&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rules of thumb:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lower heartbeat + stale timeout → faster failover, more DB chatter.&lt;/li&gt;
&lt;li&gt;Higher values → less overhead, slower reaction to node failure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Practical guidance
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Keep your &lt;strong&gt;key design&lt;/strong&gt; intentional (see Ordering chapter). It drives both ordering and partitioning.&lt;/li&gt;
&lt;li&gt;If one key is extremely “hot” (e.g., &lt;code&gt;tenant-1&lt;/code&gt;), it will map to a single partition and become a throughput bottleneck.
In that case, consider a more granular key if business semantics allow it.&lt;/li&gt;
&lt;li&gt;For near real-time delivery requirements, pure polling will always add some latency; consider CDC or broker-native transaction integrations.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Outages: retries and failed records
&lt;/h3&gt;

&lt;p&gt;Outages and transient failures are not edge cases — they’re normal: rate limits, broker downtime, flaky networks, credential rollovers.&lt;/p&gt;

&lt;p&gt;The hard part is making retries predictable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;retry too aggressively → you amplify the outage and overload your own system&lt;/li&gt;
&lt;li&gt;retry too slowly → your backlog grows and delivery latency explodes&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Namastack Outbox retry model (high level)
&lt;/h4&gt;

&lt;p&gt;Each record is processed by a handler. If the handler throws, the record is not lost — it is rescheduled for another attempt.&lt;/p&gt;

&lt;p&gt;Records move through a simple lifecycle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;NEW&lt;/code&gt; → waiting / retrying&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;COMPLETED&lt;/code&gt; → successfully processed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FAILED&lt;/code&gt; → retries exhausted (needs attention)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Default configuration knobs
&lt;/h4&gt;

&lt;p&gt;You can tune polling, batching, and retry via configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;outbox&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;poll-interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2000&lt;/span&gt;
  &lt;span class="na"&gt;batch-size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;

  &lt;span class="na"&gt;retry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;policy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exponential&lt;/span&gt;
    &lt;span class="na"&gt;max-retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;

    &lt;span class="c1"&gt;# Optional: only retry specific exceptions&lt;/span&gt;
    &lt;span class="na"&gt;include-exceptions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;java.net.SocketTimeoutException&lt;/span&gt;

    &lt;span class="c1"&gt;# Optional: never retry these exceptions&lt;/span&gt;
    &lt;span class="na"&gt;exclude-exceptions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;java.lang.IllegalArgumentException&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A good production default is &lt;strong&gt;exponential backoff&lt;/strong&gt;, because it naturally reduces pressure during outages.&lt;/p&gt;

&lt;h4&gt;
  
  
  What happens after retries are exhausted?
&lt;/h4&gt;

&lt;p&gt;At some point, you need a deterministic outcome.&lt;/p&gt;

&lt;p&gt;Namastack Outbox supports fallback handlers for exactly that case:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;retries exhausted, or&lt;/li&gt;
&lt;li&gt;non-retryable exception&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For annotation-based handlers, the fallback method must be on the same Spring bean as the handler.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Component&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderHandlers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;publisher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;OrderPublisher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;deadLetter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DeadLetterPublisher&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="nd"&gt;@OutboxHandler&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;OrderCreatedEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@OutboxFallbackHandler&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onFailure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;OrderCreatedEvent&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="nc"&gt;OutboxFailureContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;deadLetter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&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;lastFailure&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a fallback succeeds, the record is marked &lt;code&gt;COMPLETED&lt;/code&gt;.&lt;br&gt;
If there’s no fallback (or the fallback fails), the record becomes &lt;code&gt;FAILED&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Practical guidance
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Decide early what “FAILED” means in your org: alert, dashboard, dead-letter, or manual replay.&lt;/li&gt;
&lt;li&gt;Keep retry counts conservative when handlers talk to external systems; rely on backoff rather than fast loops.&lt;/li&gt;
&lt;li&gt;For critical flows, use metrics to alert when &lt;code&gt;FAILED&lt;/code&gt; records appear or when backlog grows.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;If you found this useful, I’d really appreciate a ⭐ on GitHub — and feel free to share the article or leave a comment with feedback / questions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quickstart: &lt;a href="https://outbox.namastack.io/latest/quickstart/" rel="noopener noreferrer"&gt;https://outbox.namastack.io/latest/quickstart/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Features overview (recommended): &lt;a href="https://outbox.namastack.io/latest/features/" rel="noopener noreferrer"&gt;https://outbox.namastack.io/latest/features/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Example projects: &lt;a href="https://github.com/namastack/namastack-outbox/tree/main/namastack-outbox-examples" rel="noopener noreferrer"&gt;https://github.com/namastack/namastack-outbox/tree/main/namastack-outbox-examples&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub repository: &lt;a href="https://github.com/namastack/namastack-outbox" rel="noopener noreferrer"&gt;https://github.com/namastack/namastack-outbox&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>kotlin</category>
      <category>springboot</category>
      <category>outbox</category>
    </item>
  </channel>
</rss>
