← Back to blog

Exercise: Build a Transactional Outbox Pattern in n8n

A hands-on editorial exercise: build a transactional outbox pattern in n8n so an event is only marked delivered after the downstream system confirms it.

A sealed envelope binding a business record and its outbox slip together represents one atomic database write.

Checked against the cited sources on .

Editorial exercise: what you will build and why

This is an editorial exercise: a self-guided practice task to run in your own n8n environment, not a certified assignment or a workflow we have built and tested on your behalf. The goal is to build a transactional outbox pattern in n8n so that an event sent to a downstream system is only marked delivered after that system actually confirms receipt, not the moment your workflow fires it off.

You will work with a Postgres database and a downstream target, either an HTTP endpoint or a message broker, write a business record and an outbox record together, then build a small n8n relay that detects new outbox rows, sends them onward, and marks each row processed only after confirmation comes back.

Background: the dual-write problem and what the transactional outbox pattern guarantees

The problem this pattern addresses is known as the dual-write problem. A workflow needs to update a database and notify a downstream system about that change, but these are two separate operations; without a distributed transaction, one can succeed while the other fails, leaving the two out of sync. The transactional outbox pattern avoids this by writing the business record and a record of the event to send, the outbox row, inside the very same database transaction, so the two can never diverge on their own.

It's worth being precise about what this buys you. n8n's own guidance on the pattern states plainly that duplicate delivery is always possible, so what you're actually building is reliable, confirmed at-least-once delivery, not literal exactly-once delivery. That's why the pattern only works safely alongside an idempotent consumer downstream, one that can receive the same event twice without acting on it twice.

Software architect Chris Richardson makes the same point in his own description of the pattern, writing about the consumer side of this exchange.

n8n's guidance also gives a simple operating rule for the workflow itself: mark an outbox row processed only once delivery has actually succeeded, never before. That rule is what the build steps below are structured around.

Sources: Pattern: Transactional outbox, How the Transactional Outbox Pattern Guarantees Event Delivery – n8n Blog

Prerequisites and inputs

Before starting, check that your setup matches what this exercise assumes. It's written for a developer already comfortable with the n8n Postgres node and basic error handling, not as a first workflow.

The concrete inputs are a business table you already have or create for this exercise, such as an orders table, a new outbox table with columns for a payload, a status flag and a timestamp, and the credentials n8n needs to reach both Postgres and your chosen downstream target.

Sources: Postgres | Nodes | n8n Docs, HTTP Request | Nodes | n8n Docs, RabbitMQ | Nodes | n8n Docs

Constraints

A few constraints shape how you build this, and they follow directly from how the relevant n8n nodes behave rather than from general good practice.

Sources: How the Transactional Outbox Pattern Guarantees Event Delivery – n8n Blog, Postgres Trigger | Nodes | n8n Docs, HTTP Request | Nodes | n8n Docs, Handle errors gracefully | Build | n8n Docs, Postgres | Nodes | n8n Docs

Build steps: from atomic write to confirmed delivery

Five relay stages show the transactional outbox pattern moving an event from atomic write to confirmed delivery.
An illustrative sequence of the exercise's build steps, not a screenshot of an actual n8n canvas.

With prerequisites and constraints in place, build the relay in stages inside a single n8n workflow, from the atomic write through to confirmed delivery. Each stage below maps to one or more nodes in that workflow.

Outbox relay build sequence

  1. Write the pair: In one Postgres transaction batch, insert the business row and an outbox row with status pending; if either insert fails, both roll back.
  2. Detect new rows: Pick up the pending row with a Postgres Trigger node listening for inserts on the outbox table.
  3. Publish the event: Send the row's payload to the downstream system with an HTTP Request node, or hand it to a broker such as RabbitMQ using n8n's RabbitMQ node.
  4. Check for confirmation: Branch with an If node on whether the call returned a 2xx response, treating any other result as not delivered.
  5. Mark the row processed: On the confirmed branch only, update the outbox row's status to processed with a Postgres node.
  6. Handle failure: On the unconfirmed branch, use a Stop and Error node so the run fails visibly and the attached error workflow can alert the team.

Which detection approach and which downstream target you pick changes a couple of details, but the shape of the transactional outbox pattern stays the same throughout: nothing gets marked processed until the downstream side has actually said yes.

Sources: Postgres | Nodes | n8n Docs, How the Transactional Outbox Pattern Guarantees Event Delivery – n8n Blog, Postgres Trigger | Nodes | n8n Docs, RabbitMQ | Nodes | n8n Docs, HTTP Request | Nodes | n8n Docs, If | Nodes | n8n Docs, Handle errors gracefully | Build | n8n Docs

Completion criteria and troubleshooting

You'll know the exercise is done when the workflow behaves correctly under both success and failure. Treat the following as your completion check, and deliberately break the downstream connection at least once so you test the failure path, not only the happy path.

If rows get stuck in pending, check two things first: whether the workflow is actually published, since a Postgres Trigger node's underlying database trigger exists only while it is, and whether the HTTP Request node is receiving a genuine 2xx response rather than a redirect or an error page disguised as success.

Sources: Postgres | Nodes | n8n Docs, HTTP Request | Nodes | n8n Docs, How the Transactional Outbox Pattern Guarantees Event Delivery – n8n Blog, Handle errors gracefully | Build | n8n Docs, Postgres Trigger | Nodes | n8n Docs

Reflection and suggested solution sketch

A reasonable solution sketch: two Postgres queries in one transaction batch for the write, a Postgres Trigger node for detection, an HTTP Request node for publish, an If node checking the status code, a Postgres node on the success branch to mark the row processed, and a Stop and Error node on the failure branch feeding an Error Trigger workflow that notifies you.

If your downstream target is a message broker instead of an HTTP endpoint, swap the HTTP Request node and its status-code check for n8n's RabbitMQ node, and update the outbox row only once you have an equivalent acknowledgment from the broker step.

This is a personal or team practice exercise, not a production-ready implementation: the steps above are an editorial design built from documented n8n capabilities. Before relying on this pattern against a shared production instance, treat it as a starting point your own team adapts to its own schema, downstream target and failure modes.

Sources: How the Transactional Outbox Pattern Guarantees Event Delivery – n8n Blog, Postgres Trigger | Nodes | n8n Docs, Postgres | Nodes | n8n Docs, Handle errors gracefully | Build | n8n Docs, HTTP Request | Nodes | n8n Docs, If | Nodes | n8n Docs

Put this into practice

Hands-on n8n challenges

Pick a challenge and build a working workflow in your own n8n environment, with five progressive tips per challenge.

Try a hands-on challenge

For your team

Custom n8n training programs for one team or department, run on your own n8n instance with your own tools and data.

Training for your team