The outbox is the smallest correct answer to the dual write problem, and the implementation is one insert and a worker.
CREATE TABLE outbox (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
topic VARCHAR(128) NOT NULL,
payload JSON NOT NULL,
created_at DATETIME(6) NOT NULL,
published_at DATETIME(6) NULL,
INDEX idx_unpublished (published_at, id)
);
-- the relay, every second:
-- SELECT ... WHERE published_at IS NULL ORDER BY id LIMIT 100
The index on (published_at, id) is what keeps the relay query fast as the table grows, and without it the relay scans everything ever published. Pruning is a separate job and is needed within weeks. Polling every second is unglamorous and works; reading the binlog is the sophisticated version and is considerably more machinery for the same guarantee.