Writing a row and publishing a message are two systems, and any ordering of them has a window where one succeeded and the other did not.
DB::transaction(function () use ($order) {
$order->save();
Outbox::create([
'topic' => 'orders.placed',
'payload' => json_encode($order->toEvent()),
]);
});
// a separate process reads the outbox and publishes,
// marking rows as sent. at-least-once, by construction.
Both writes are in one transaction because they are in one database, which is the entire trick — the message becomes durable at the same instant as the state change it describes. The publisher is a separate concern and may publish a message twice if it crashes after publishing and before marking, so consumers must be idempotent regardless. The outbox table needs pruning, and a publisher that falls behind is an unbounded queue in your primary database.