<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/reliable-message-delivery-the-transactional-outbox-pattern-with-okapi-dgj2qqz0f" -->

---
title: Reliable Message Delivery: the Transactional Outbox...
description: 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...
canonical: https://daily.dev/posts/reliable-message-delivery-the-transactional-outbox-pattern-with-okapi-dgj2qqz0f
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Reliable Message Delivery: the Transactional Outbox Pattern With Okapi | daily.dev
og:description: 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...
og:url: https://daily.dev/posts/reliable-message-delivery-the-transactional-outbox-pattern-with-okapi-dgj2qqz0f
og:image: https://api.daily.dev/og/posts/DGJ2qQZ0f.png
og:image:alt: Reliable Message Delivery: the Transactional Outbox Pattern With Okapi
og:image:width: 1200
og:image:height: 630
og:locale: 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 · 27 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

### What is the transactional outbox pattern and why do I need it for Kafka or webhook notifications in a Spring Boot app?

It solves the dual-write problem where saving to a database and notifying another service via webhook or Kafka aren't part of the same transaction. Instead of calling the downstream directly, you write a message to an outbox table in the same transaction as your business data, and a separate background processor delivers it afterward, giving atomicity on the write and eventual delivery on the notification.

_daily.dev surfaces practical write-ups like this for developers wiring reliable messaging into Spring Boot services._

### How do I implement the transactional outbox pattern in Kotlin without building it from scratch?

Use okapi, a Kotlin library from SoftwareMill released at version 1.0.0 under Apache-2.0. It provides Postgres and MySQL storage modules, HTTP and Kafka delivery, Spring Boot autoconfiguration with automatic Liquibase schema setup, a Micrometer metrics module, and a framework-agnostic core usable in plain Kotlin services like Ktor apps or CLI tools.

_Developers evaluating outbox libraries can track releases like this one on daily.dev before picking a dependency._

### Does okapi guarantee exactly-once delivery or message ordering for outbox messages?

No, okapi guarantees at-least-once delivery, not exactly-once, so consumers must be idempotent since a crash after sending but before marking a row delivered causes a resend. Ordering is also not guaranteed: the processor claims rows oldest first, but retries, parallel delivery via sendAsync, configurable concurrency, and multiple service instances mean a later message can arrive before an earlier one.

_Teams weighing outbox trade-offs like idempotency and ordering can find deep dives like this 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.

## Similar posts on daily.dev

- [How to Implement the Outbox Pattern in Go and PostgreSQL](https://daily.dev/posts/how-to-implement-the-outbox-pattern-in-go-and-postgresql-ygygbpymj) · freeCodeCamp · 9 upvotes · 0 comments
- [How to Implement the Outbox Pattern in Distributed Systems](https://daily.dev/posts/how-to-implement-the-outbox-pattern-in-distributed-systems-0xhg3kran) · C\# Corner · 4 upvotes · 0 comments
- [Saved the Order, Lost the Event: Why You Need the Outbox Pattern](https://daily.dev/posts/saved-the-order-lost-the-event-why-you-need-the-outbox-pattern-thxdod4hv) · The T-Shaped Dev · 21 upvotes · 0 comments
- [You Wrote To Two Databases. Did Both Succeed?](https://daily.dev/posts/you-wrote-to-two-databases-did-both-succeed--1vbubwl2g) · ITNEXT · 0 upvotes · 0 comments
- [Cloud Bits: Breaking the Double Write – A Guide to Distributed Data Consistency – Distributed Computing Musings](https://daily.dev/posts/cloud-bits-breaking-the-double-write-a-guide-to-distributed-data-consistency-distributed-comput-i2embckph) · Distributed Computing Musings · 0 upvotes · 0 comments

---

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)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"Reliable Message Delivery: the Transactional Outbox Pattern With Okapi","url":"https://daily.dev/posts/reliable-message-delivery-the-transactional-outbox-pattern-with-okapi-dgj2qqz0f","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/reliable-message-delivery-the-transactional-outbox-pattern-with-okapi-dgj2qqz0f"},"datePublished":"2026-08-10T10:23:01.330Z","dateModified":"2026-09-14T09:08:14.941Z","description":"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...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/ecb74fda2d3af2b3e7372d5635854194?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/ecb74fda2d3af2b3e7372d5635854194?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"SoftwareMill","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"SoftwareMill","logo":"https://media.daily.dev/image/upload/t_logo,f_auto/v1/logos/30827c94697f47fcbc88ff9b59fa226f","url":"https://daily.dev/sources/softwaremill"},"commentCount":1,"discussionUrl":"https://daily.dev/posts/reliable-message-delivery-the-transactional-outbox-pattern-with-okapi-dgj2qqz0f","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":27},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":1}],"keywords":"kotlin,kafka,spring-boot","timeRequired":"PT12M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"SoftwareMill","item":"https://daily.dev/sources/softwaremill"},{"@type":"ListItem","position":3,"name":"Reliable Message Delivery: the Transactional Outbox Pattern With Okapi"}]}
{"@context":"https://schema.org","@type":"WebPage","@id":"https://daily.dev/posts/reliable-message-delivery-the-transactional-outbox-pattern-with-okapi-dgj2qqz0f","comment":[{"@type":"Comment","text":"The outbox dashboard needs to show oldest queued-event age beside queue depth. One stuck message can hide in a healthy-looking average.","datePublished":"2026-08-15T22:05:29.016Z","url":"https://daily.dev/posts/DGJ2qQZ0f#c-jXNxTsCw1","author":{"@type":"Person","name":"Agustin Barrientos","url":"https://daily.dev/agustinbarrientos","image":"https://media.daily.dev/image/upload/s--5ayxQnqn--/f_auto/v1788281802/avatars/avatar_wQYYVe5Tbj0NJ7C7qPoa8?_a=BAMAMicg0"}}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/reliable-message-delivery-the-transactional-outbox-pattern-with-okapi-dgj2qqz0f#faq","mainEntity":[{"@type":"Question","name":"What is the transactional outbox pattern and why do I need it for Kafka or webhook notifications in a Spring Boot app?","acceptedAnswer":{"@type":"Answer","text":"It solves the dual-write problem where saving to a database and notifying another service via webhook or Kafka aren't part of the same transaction. Instead of calling the downstream directly, you write a message to an outbox table in the same transaction as your business data, and a separate background processor delivers it afterward, giving atomicity on the write and eventual delivery on the notification. daily.dev surfaces practical write-ups like this for developers wiring reliable messaging into Spring Boot services."}},{"@type":"Question","name":"How do I implement the transactional outbox pattern in Kotlin without building it from scratch?","acceptedAnswer":{"@type":"Answer","text":"Use okapi, a Kotlin library from SoftwareMill released at version 1.0.0 under Apache-2.0. It provides Postgres and MySQL storage modules, HTTP and Kafka delivery, Spring Boot autoconfiguration with automatic Liquibase schema setup, a Micrometer metrics module, and a framework-agnostic core usable in plain Kotlin services like Ktor apps or CLI tools. Developers evaluating outbox libraries can track releases like this one on daily.dev before picking a dependency."}},{"@type":"Question","name":"Does okapi guarantee exactly-once delivery or message ordering for outbox messages?","acceptedAnswer":{"@type":"Answer","text":"No, okapi guarantees at-least-once delivery, not exactly-once, so consumers must be idempotent since a crash after sending but before marking a row delivered causes a resend. Ordering is also not guaranteed: the processor claims rows oldest first, but retries, parallel delivery via sendAsync, configurable concurrency, and multiple service instances mean a later message can arrive before an earlier one. Teams weighing outbox trade-offs like idempotency and ordering can find deep dives like this on daily.dev."}}]}
```

