The design we start from when forms, payments, CRMs and schedules need to trigger work across your tools, sometimes with an AI step in the middle, without losing or doubling a record. It is a reference design, not a client system, and it keeps the same shape on n8n or in code; the queue behaviour it relies on is cited with the date we checked it.
A form webhook is stored before ingress returns 200, and a nightly schedule adds changed records; both land in a queue keyed by event id. The enrich worker finishes; the CRM worker retries, then parks the job on the dead-letter list, where an operator replays it with the same key.
The problem
What this architecture solves
Point-to-point automations work until volume or failure arrives: a webhook fires twice, a CRM is down for a minute, a model call times out, and someone finds a duplicate contact or a lead that never arrived. This design treats every trigger as an event with an id, stores it first, and lets workers process it at their own pace, safe to retry and easy to replay.
No event is lost: it is stored before the sender gets its 200.
No write happens twice: every worker checks the event id first.
A failure lands on a list a person can read and replay, not in a log nobody opens.
An AI step is one worker among others, with its output checked against a schema.
Use it when several systems trigger work, volumes vary or a duplicate costs money. A single low-volume Zap that copies a form into a sheet does not need it.
Components
Components and the options for each
Event sources
Form webhooks, payment and CRM events, and schedules that sweep systems without webhooks for changed records.
Webhooks from your tools
Stripe events
Scheduled sweeps
Ingress endpoint
Verifies the signature, writes the raw event with its id and only then returns 200. No business logic lives here.
Cloudflare Workers
n8n webhook node
Next.js route on Vercel
Event log
Every event as received, so a run can be traced and replayed from the original payload.
Cloudflare D1
Supabase Postgres
Durable queue
Holds jobs until a worker succeeds, with a retry limit and a dead-letter list for jobs that run out of retries.
Cloudflare Queues
n8n in queue mode
Postgres job table
Workers
Small steps that each do one thing (enrich, write to the CRM, notify) and check the event id before they write.
n8n workflows
Make or Zapier for simple steps
Python or TypeScript workers
Model step
A worker that classifies, extracts or drafts; its output must pass a schema check before the next step uses it.
OpenAI
Anthropic
Google Gemini
Open-weight model you host
Idempotency store
Which event ids have completed which writes, so a retry or replay skips work already done.
Supabase
Cloudflare D1
Keys on your CRM records
Dead-letter list and replay
Jobs that ran out of retries, with payload, step and last error; an operator fixes the cause and replays with the same id.
Queue dead-letter queue
Admin page with replay
n8n error workflow
Data flow
How data moves, step by step
01A source fires: a signed webhook, or a schedule that finds changed records.
02Ingress verifies the signature, stores the raw event with its id and returns 200.
03The event becomes one or more jobs on the durable queue, keyed by event id.
04Workers pick up jobs; each checks the idempotency store before writing anything.
05A model step classifies or extracts; output that fails the schema check goes to a person, not onward.
06A failed write retries with backoff; after the last retry the job moves to the dead-letter list and the owner is alerted.
07The owner fixes the cause and replays; the same event id means finished writes are skipped.
How it works
Design decisions
Mechanism 01
Store, then acknowledge
Senders retry when they do not get a quick 200, and some eventually give up. So ingress only verifies and stores before replying. The slow work happens later, from the queue, so a sender never waits on your CRM or a model.
What's the alternative? Process inside the webhook call
Fewer parts, until a slow step causes a timeout, the sender retries and the work runs twice.
Mechanism 02
The event id is the idempotency key
Queues deliver at least once, so duplicates are normal rather than rare accidents. Every write is keyed by event id plus step name, and a worker that finds the key completed skips the write. Retries and replays become safe by construction.
What's the alternative? Trust the sender not to repeat itself
Fine in testing; in production the first network blip creates a duplicate contact or a double charge.
Mechanism 03
Failures are parked, not lost
A job that runs out of retries moves to a dead-letter list with its payload, step and last error; without one, a queue deletes such messages for good. The owner sees a short, readable list and replays once the cause is fixed.
What's the alternative? Retry forever
Hides the failure, piles load on a broken system and delays everything queued behind it.
Mechanism 04
n8n or code, same shape
n8n in queue mode already separates a main instance that receives webhooks from workers that run executions, which fits this design. Steps that need strict idempotency or heavy volume move to code workers on the same queue.
What's the alternative? One giant workflow
Easy to draw, hard to retry: a failure halfway forces a rerun of everything before it.
Reference diagram
The whole design on one diagram
Drawn to the same legend as our other reference architectures, so you can compare them box for box.
System of recordIdempotency storeCompleted event ids
HumanOps ownerAlerted, then replays
Two webhooks and a nightly schedule reach an ingress that logs each event and queues it. An enrich worker and a model step feed a CRM writer that upserts with idempotency keys; jobs out of retries go to the dead-letter list, which alerts the ops owner to replay.
Diagram with 13 parts: Form webhook (source), Payment event (source), Schedule (source), Ingress endpoint (processing), Event log (system of record), Durable queue (processing), Enrich worker (processing), Model step (model), CRM writer (processing), Dead-letter list (control), Your CRM (system of record), Idempotency store (system of record), Ops owner (human).
Connections:
Form webhook → Ingress endpoint (signed POST)
Payment event → Ingress endpoint (event)
Schedule → Ingress endpoint (changed records)
Ingress endpoint → Event log (raw copy)
Ingress endpoint → Durable queue (event id)
Durable queue → Enrich worker (job)
Durable queue → Model step (job)
Enrich worker → CRM writer (enriched record)
Model step → CRM writer (checked fields)
CRM writer → Your CRM (upsert)
CRM writer → Idempotency store (done key)
CRM writer → Dead-letter list (out of retries)
Dead-letter list → Ops owner (alert)
How to read these diagrams
Source· in this diagram
Where a request or data enters: a chat widget, a WhatsApp message, a phone call, a webhook, a schedule or an approved document set.
Processing· in this diagram
Ordinary code we write and test: ingress endpoints, routing, retrieval, queues, workers and tool calls. It behaves the same way every time.
Model· in this diagram
A language, speech or embedding model call. Its output is treated as a draft and checked before it can reach a customer or a system.
Control· in this diagram
A gate that can stop, hold or reroute work: input checks, citation checks, send rules, transfer rules and dead letters.
Human· in this diagram
A customer or someone on your team. An arrow into a person is the answer, the escalation or the approval path.
System of record· in this diagram
Where state lives and survives a restart: your CRM, helpdesk or calendar through its API, a search index, a log.
→Arrows run in the order data moves: left to right, or top to bottom on a phone.
→A label on an arrow names what travels along it, such as a question, an event id or a ticket.
→Named products are options from our build stack; “your … via its API” means the system you already run.
→Each diagram is a reference design, not a drawing of any client’s system. Component choices are made per project.
When does an automation need a queue instead of a direct webhook-to-CRM flow?
When several systems trigger it, volumes jump, a step is slow (a model call, a busy API) or a duplicate costs money. If one form copies into one sheet and nobody minds a rare miss, a direct flow is fine and cheaper to keep.
What happens to events that arrive while our CRM is down?
They wait. Each event is already stored and queued, so the CRM worker retries with backoff; if the outage outlasts the retries, jobs park on the dead-letter list and are replayed once the CRM is back.
Can an AI step sit in the middle of an event-driven workflow safely?
Yes, as one worker with a narrow job: classify, extract or draft. Its output must pass a schema check before the next step uses it, it never writes to a system directly, and anything that fails goes to a person with the raw output attached.
Does this design still work if our team builds in n8n?
Yes. n8n’s queue mode already separates the instance that receives webhooks from the workers that run executions. We add the event log, idempotency keys and a dead-letter list around it, and move only the strictest steps to code.
How do we replay a failed job without creating duplicates?
Replay the original event with its original id. Each write checks the idempotency store, so finished steps are skipped and only the failed ones run again. Replaying a fresh copy with a new id is what creates duplicates.
Who should own the dead-letter list?
One named person on your operations side, with a backup. They get an alert when the list grows, fix the cause or ask us, and replay. A dead-letter list nobody owns is a slower way of losing data.
Next step
Have a workflow in mind? Let’s scope it together.
Book a free 30-minute strategy call. Bring one process you want to automate and we will talk through scope, integrations and a sensible first step.