---
title: "Reliable Message Delivery: the Transactional Outbox Pattern With Okapi"
url: https://daily.dev/posts/reliable-message-delivery-the-transactional-outbox-pattern-with-okapi-dgj2qqz0f
source_url: https://softwaremill.com/transactional-outbox-with-okapi
type: article
source: "SoftwareMill"
published: 2026-08-10T10:23:01.330Z
updated: 2026-08-24T07:02:53.402Z
tags: ["kotlin", "kafka", "spring-boot"]
reading_time: 12
upvotes: 26
comments: 1
language: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Reliable Message Delivery: the Transactional Outbox Pattern With Okapi

**[SoftwareMill](https://daily.dev/sources/softwaremill)** · 12 min read · 26 upvotes · 1 comments

## Summary

Okapi is a new Kotlin library (v1.0.0) from SoftwareMill that implements the transactional outbox pattern to solve the dual-write problem. It supports Postgres and MySQL for storage, and HTTP and Kafka for delivery. On Spring Boot, it uses autoconfiguration to set up the outbox table automatically via Liquibase. The library is at-least-once delivery (not exactly-once), uses polling rather than CDC, and does not guarantee message ordering. The post walks through adding Okapi to a Spring Boot + Postgres service with a concrete code example showing how a single publish() call inside an existing @Transactional method atomically writes both the business row and the outbox entry. It also covers retry behavior, failure classification, Micrometer metrics, and non-Spring usage.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://softwaremill.com/transactional-outbox-with-okapi>

## Questions this post answers

### How do I implement the transactional outbox pattern in a Kotlin Spring Boot app with Postgres?

Add the okapi-bom, okapi-core, okapi-postgres, okapi-http, and okapi-spring-boot dependencies. On startup, okapi auto-creates the okapi_outbox table via its bundled Liquibase changelog. Inside your existing @Transactional method, inject SpringOutboxPublisher and call publish() with an OutboxMessage and delivery info right after your repository save. The outbox row and business row commit atomically, and a background processor delivers the message within about one second.

_Teams shipping reliable event delivery from Kotlin services track patterns like this on daily.dev._

### Does Okapi guarantee exactly-once message delivery or at-least-once?

Okapi guarantees at-least-once delivery, not exactly-once. Once a message is committed to the outbox table it is as durable as any other row, but if the processor sends a message and crashes before marking it delivered, it will resend on restart. Consumers must be idempotent. Deduplication must rely on something the publisher puts in the payload, such as a business key or the OutboxId returned by publish().

_Developers designing idempotent consumers for outbox-based systems find related trade-off discussions on daily.dev._

### What happens when HTTP delivery fails in Okapi and how many retries does it attempt?

Okapi classifies HTTP failures as retriable or permanent. A 5xx, 429, 408, or connection error keeps the row PENDING and increments the retries counter; after okapi.processor.max-retries attempts (default 5), the row becomes FAILED — six total attempts including the first. Any other non-success response (e.g. a plain 4xx) is a permanent failure with no retries. Both the retriable status code set and the max-retries budget are configurable.

_Developers tuning outbox retry policies for production services find related Okapi and messaging content on daily.dev._

## Community discussion

Top comments from developers on daily.dev.

**@agustinbarrientos** · 0 upvotes

> The outbox dashboard needs to show oldest queued-event age beside queue depth. One stuck message can hide in a healthy-looking average.

---

Tags: [#kotlin](https://daily.dev/tags/kotlin), [#kafka](https://daily.dev/tags/kafka), [#spring-boot](https://daily.dev/tags/spring-boot)

[View this post on daily.dev](https://daily.dev/posts/reliable-message-delivery-the-transactional-outbox-pattern-with-okapi-dgj2qqz0f)
