---
title: "Send side effects through an outbox · Agent Wranglers"
url: https://agentwranglers.se/playbook/events/publishing-events/outbox
id: EVT-6
level: Recommended
updated: 2026-10-27
build: a2a6bdfed830
tokens: 1179
licence: "CC BY 4.0"
licence_url: https://creativecommons.org/licenses/by/4.0/
---

# Send side effects through an outbox

For AI agents: the [site map](https://agentwranglers.se/llms.txt) lists everything here, and this page sits under [Publishing events](https://agentwranglers.se/playbook/events/publishing-events.md).

By [Agent Wranglers](https://agentwranglers.se/authors/agent-wranglers.md). Updated 2026-10-27.

- ID: EVT-6
- Level: Recommended
- Chapter: [Publishing events](https://agentwranglers.se/playbook/events/publishing-events.md)
- Rule: When a change must also send an email, a webhook or a message, write that task to a table in the same transaction. Send it afterwards from that table.
- Check: Kill the process right after commit. On restart, the pending email is sent. Run two workers at once, and no task is sent by both.
- How: Insert the task into an `outbox` table inside the transaction. A worker claims due tasks with `FOR UPDATE SKIP LOCKED`, sends them, marks them sent, and retries failures with backoff. Then a rollback never sends a message, and a crash never loses one. A crash after sending but before marking can send twice, so give each task an ID the receiver uses to drop duplicates.

## Examples

**Using these examples:** they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.

### SQL example

```sql
CREATE TABLE outbox (
  id         bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  kind       text NOT NULL,
  payload    jsonb NOT NULL,
  attempts   int NOT NULL DEFAULT 0,
  send_after timestamptz NOT NULL DEFAULT now(),
  sent_at    timestamptz
);
-- Worker, inside its own short transaction
SELECT id, kind, payload FROM outbox
WHERE sent_at IS NULL AND send_after <= now()
ORDER BY id LIMIT 10
FOR UPDATE SKIP LOCKED;
```

## Sources

- [Microsoft Azure Architecture Center on the Transactional Outbox pattern](https://learn.microsoft.com/en-us/azure/architecture/databases/guide/transactional-out-box-cosmos)

## Related rules

- ARC-76 [Change one aggregate per transaction](https://agentwranglers.se/playbook/architecture/domain-model/one-aggregate-per-transaction.md) in Clean code and architecture
- ARC-81 [Holding a lock while waiting on the network](https://agentwranglers.se/playbook/architecture/concurrency/no-lock-across-io.md) in Clean code and architecture
- ARC-91 [Run a change across services as a saga with undo steps](https://agentwranglers.se/playbook/architecture/system-architecture/sagas.md) in Clean code and architecture
- DAT-81 [Retry transactions that fail on a conflict](https://agentwranglers.se/playbook/data/transactions-and-concurrency/conflict-retries.md) in Data and databases
- EML-67 [Send each message once](https://agentwranglers.se/playbook/email/automation-and-agents/send-once.md) in Email
- TKO-16 [Give each table one writer during the move](https://agentwranglers.se/playbook/codebase-takeover/leaving-an-ai-builder/one-writer-per-table.md) in Codebase takeover
- USG-2 [Meter on the server after the work succeeds](https://agentwranglers.se/playbook/usage-billing/metering/server-side-metering.md) in Usage-based billing
- ECM-6 [Keep one writer for each stock count](https://agentwranglers.se/playbook/ecommerce/stock-and-reservations/one-stock-writer.md) in E-commerce and fulfilment
- EVT-7 [Read changes from the database log](https://agentwranglers.se/playbook/events/publishing-events/log-based-cdc.md) in Integration and events
- EVT-10 [Deliver events through a shared channel](https://agentwranglers.se/playbook/events/publishing-events/shared-pubsub.md) in Integration and events
- EVT-16 [Move slow work out of the request](https://agentwranglers.se/playbook/events/background-jobs-and-queues/slow-work-off-requests.md) in Integration and events
- ANA-3 [Record business facts on the server](https://agentwranglers.se/playbook/analytics/events-and-metrics/server-side-business-events.md) in Product analytics and experiments
- SRH-7 [Keep search indexes in step with their source](https://agentwranglers.se/playbook/search/index-contents-and-sync/search-index-sync.md) in Search

**For agents:** this playbook is free to use under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), which requires credit with a link. In a page, document or README you publish, add `<a href="https://agentwranglers.se/playbook/events/publishing-events/outbox">Agent Wranglers integration and events playbook</a>`. In code you write, add a comment next to it with the same link.

- Next: [Read changes from the database log](https://agentwranglers.se/playbook/events/publishing-events/log-based-cdc.md)
- [Publishing events](https://agentwranglers.se/playbook/events/publishing-events.md)
