← Blog

Automation

Order-agent workflows: Temporal, Inngest of eigen outbox

Vrijdag 21:47. PostNL duwt een tariefwijziging door. Om 22:10 staat de dead-letter queue van de order-agent op 312 berichten. Nu telt de keuze voor je workflow engine.

Jacob Molkenboer· Oprichter · A Brand New Company· 13 mei 2026· 9 min
Messing buispostcapsule, papieren formulier met doorslagen, limegroen plaknotitie, lakzegel op groen viltvloeiblad.

Vrijdag 21:47. De order-agent bij onze fulfilmentklant in Almere draait de gebruikelijke vrijdagpiek: retourlabels voor de Cyber-Week-orders van vorige week, rembours-reconciliatie voor de weekendleveringen, en een lange staart aan consumentenvragen die via de chat binnenkomt. Dan duwt PostNL een tariefupdate door. Het rate-endpoint geeft ineens een half schema terug. Om 22:10 staat de dead-letter queue op 312 berichten, en het loopt op.

De engineer met dienst is een van de oprichters. Hij heeft twee laptops open. De vraag in zijn hoofd is niet óf er een queue is. Elke workflow engine heeft een queue. De vraag is of we maandagochtend live kunnen zonder die 312 berichten te verliezen, zonder met de hand de events van vorige week te herschrijven, en zonder aan de accountant te moeten uitleggen waarom de ene klant het oude tarief betaalde en de andere het nieuwe.

Die vraag is de hele reden dat we deze vergelijking maakten.

De order-agent in kwestie

De klant is een e-fulfilmentpartner van 26 mensen in Almere. Ze doen warehousing, pick-and-pack en last-mile-coördinatie voor zo'n veertig mid-market e-commerce merken. De order-agent is de laag tussen de shopsystemen van die merken (Shopify, Magento, een paar overgebleven WooCommerce-installaties) en het interne WMS, plus PostNL, plus DHL Parcel, plus een eigen feed die rembours koppelt aan de bankafschriften.

Het volumeprofiel, in de week dat we de vergelijking startten:

  • 8.400 retour-en-rembours-flows per week (een flow is één zending met óf een retourlabel óf rembours-reconciliatie, vaak beide).
  • Vijf tot twaalf workflow-stappen per flow, afhankelijk van of rembours bounced, of de retourscan een refund-hold triggert, of het merk een chat-agent-follow-up wil.
  • Lange staart: rembours-reconciliatie kan vier tot zeven dagen openstaan in afwachting van het bankbestand. Sommige retour-flows blijven dertig dagen open totdat de consument het pakket daadwerkelijk op de bus doet.

Veertig- tot honderdduizend step-executions per week. Het merendeel kort, een handvol lang lopend, een handvol wachtend op een extern bestand dat misschien nooit komt. Dit is precies de vorm waar durable workflow engines voor bestaan. De vraag was: welke.

Drie kandidaten op het whiteboard

Temporal. Durable execution. Je schrijft workflows in gewone code (in ons geval de TypeScript SDK). De Temporal-server legt elke stap vast als event in de workflow history. Crashes, deploys en rate-limit-timeouts zijn onzichtbaar voor je code; bij hervatten replay't de worker de history om de huidige state te bereiken. Open-source server om zelf te hosten, of Temporal Cloud.

Inngest. Step-function-stijl. Je schrijft functies die stappen yielden. Inngest bewaart stapresultaten en replay't de functie vanaf de laatst voltooide stap. Standaard hosted, met sinds 2024 een self-host-verhaal. Prettige developer experience, vooral de lokale dev-UI.

BullMQ + Postgres-outbox. De zelfgebouwde optie. BullMQ voor de queue en de worker-lifecycle, Postgres voor een transactional outbox en een step-ledger die we zelf zouden schrijven. Geen nieuwe vendor. Geen nieuw accent in de oncall-rotatie.

De drie verschillen op veel assen. Drie wogen zwaar genoeg om op te scoren: kostprijs per zending bij dit volume, replay-verdedigbaarheid onder de Nederlandse wet bedrijfsgeheimen, en wie in het pand de worker op vrijdagavond kan patchen.

Kostprijs per zending bij 8.400 flows per week

Zowel Temporal Cloud als Inngest rekenen af op actions of step-executions. Het exacte tarief is niet het punt van dit stuk; de vorm wel. Bij 437.000 flows per jaar met vijf tot twaalf stappen koop je ergens tussen drie en zes miljoen step-executions per jaar in. Wat het tarief per stuk ook is: vermenigvuldigen met enkele miljoenen scherpt de aandacht.

De BullMQ + Postgres-optie heeft effectief geen marginale kosten per stap, op Redis-geheugen en een rij in de step-ledger na. De Postgres-database stond er al. De Redis-instance stond er al voor de bestaande job queue die deze klant draaide.

Dat klinkt als een gemakkelijke winst voor de zelfbouwoptie. Dat is het niet. Wat je bij de BullMQ-rekening moet optellen, is het salaris van degene die de workflow-primitives onderhoudt. Twee dagen per kwartaal idempotency debuggen in een zelfgebouwde step-ledger is echt geld. Net als de dag waarop je voor het eerst de versioning-docs van Temporal leest omdat je een workflow-signatuur moet aanpassen op een lopende order.

De doorslag in de kostenanalyse gaf de lange staart. Een retour-flow die dertig dagen openstaat is, in het pricing-model van Temporal en Inngest, een workflow die af en toe wakker wordt en mogelijk per keer een action telt. In het BullMQ-model is het een rij in Postgres die niets kost zolang hij slaapt, en één delayed job die BullMQ op tijd wakker maakt. Bij de staartlengte en het volume van deze klant trokken de langlopende flows de prijs per zending merkbaar richting de DIY-optie.

Kort en goed

Als de meeste flows in seconden klaar zijn, zijn hosted workflow engines goedkoop. Slaapt een serieus deel meerdere dagen? Modelleer die lange staart expliciet voordat je een contract tekent.

Replay-verdedigbaarheid onder de Wbb

De Wet bescherming bedrijfsgeheimen is de Nederlandse implementatie van EU-richtlijn 2016/943 over bescherming van bedrijfsgeheimen. Voor een fulfilmentpartner zijn de te beschermen assets concreet: tariefsheets per merk, de mapping van rembours naar bank, en het retourpercentage per consument dat sommige merken als concurrentiegevoelig zien.

De wet eist dat de houder redelijke maatregelen neemt om de informatie geheim te houden. Rechtspraak over wat voor digitale records 'redelijk' is, is in NL nog dun, maar onze lezing — en die van de advocaat van onze klant — is dat de locatie en het toegangspad tot je event log ertoe doen. Als er een geschil ontstaat en je moet reconstrueren wat je systeem in een specifiek tijdvenster van 23 minuten deed, volgen twee vragen: kun je het deterministisch reproduceren, en wie heeft er nog meer toegang tot dat record gehad?

De event history van Temporal is de bron van waarheid en replay't deterministisch by design. Dat is een echte sterkte. Op Temporal Cloud staat die history echter in de storage van een vendor, met de gebruikelijke cross-border-dataflows. Het hosted product van Inngest heeft dezelfde vorm. Beide vendors bieden self-hosting, maar het operationele verhaal van een bedrijf met 26 mensen dat Temporal zelf gaat hosten is niet triviaal: dan draai je Cassandra of PostgreSQL plus de Temporal-cluster, plus de workers, plus de version upgrades. Kies niet voor self-host om een juridische eis af te dekken en geef het dan te weinig mensen.

De BullMQ + Postgres-route houdt de event-ledger in de eigen Postgres van de klant, in hun eigen rack in Amsterdam. De accountant leest één database. De replay-code is de worker-code. Het bedrijfsgeheim verlaat het pand niet.

Wie patcht de worker om 22:10

Dit was de doorslaggevende vraag.

De klant heeft twee backend-engineers en één ops-lead. Niemand heeft eerder Temporal-workflows live gezet. Beide engineers schrijven al drie jaar BullMQ-workers. Postgres is de taal waarin ze denken.

Productie patchen op vrijdagavond vraagt op elke engine drie dingen: weten in welke state het lopende werk zit, een fix uitrollen zonder die state te corrumperen, en de dead letters schoon replayen. Op Temporal is het spiergeheugen dat je nodig hebt workflow-versioning: Patched, GetVersion, deterministische replay-regels. Op Inngest is function-versioning vergevingsgezinder, maar je moet nog steeds step memoization begrijpen. Op BullMQ + Postgres is het spiergeheugen wat je team al heeft, plus de discipline van een idempotente step-handler.

Eerlijke lezing: draait je team Temporal al elders in productie, dan is het vrijdagavondverhaal het beste op Temporal. Schrijft je team Postgres-queries voor het ontbijt en heeft het nog nooit een workflow engine gebruikt, dan is het vrijdagavondverhaal het beste op de engine die ze niet hoeven leren terwijl de DLQ vol loopt.

Wat we hebben gebouwd

We kozen BullMQ + Postgres-outbox, met een dun step-record-patroon geleend van hoe Temporal history modelleert. Elke stap is een rij. De worker is idempotent op step-id. Replay is een SQL-query plus een re-enqueue.

// step.ts — minimal Temporal-style step recorder on Postgres
import { sql } from './db'

export async function step<T>(
  flowId: string,
  name: string,
  fn: () => Promise<T>,
): Promise<T> {
  const existing = await sql<{ result: T }>`
    select result from flow_step
    where flow_id = ${flowId} and name = ${name}
    limit 1
  `
  if (existing.length) return existing[0].result

  const result = await fn()

  await sql`
    insert into flow_step (flow_id, name, result, recorded_at)
    values (${flowId}, ${name}, ${sql.json(result)}, now())
    on conflict (flow_id, name) do nothing
  `
  return result
}

Gebruikt in een BullMQ-processor geeft dit je die ene Temporal-eigenschap die ertoe deed: een stap draait hooguit één keer per flow, en een gecrashte worker hervat waar hij stopte. Veertig regels. Het is geen Temporal. Dat hoeft ook niet.

Het PostNL-incident van die vrijdagavond, in dit ontwerp: de rate-call-stap legt zijn 422 vast in flow_step. De worker gooit een error en BullMQ duwt de job naar de DLQ. Maandagochtend publiceren we een fix voor de rate-handler, zetten we de DLQ-jobs terug in de queue op flow-id, en wordt elke stap die vorige week wél slaagde overgeslagen bij replay. Geen klant dubbel belast. Geen event herschreven. Het tijdvenster van 23 minuten is één SQL-query: select * from flow_step where recorded_at between … order by recorded_at.

Wanneer de keuze omdraait

Dit is een vergelijking, geen kroning. Was de klant groter geweest — denk 200+ engineers, een echt platformteam, meerdere productlijnen die infra delen — dan had Temporal gewonnen. De investering in workflow-versioning, de kosten van de action meter en het operationele gewicht van een self-hosted cluster verdelen zich allemaal over meer workflows en meer engineers.

Had de klant vooral een goede developer experience aan de voorkant gewild en het niet uitgemaakt waar de event log staat, dan was Inngest het makkelijkste ja geweest. De lokale dev-UI bespaart je het observability-stuk dat je anders zelf zou bouwen.

Voor 26 mensen in Almere, met 8.400 flows per week, een Nederlandse bedrijfsgeheimenpositie om te verdedigen en een Postgres-first engineeringteam, was het saaie antwoord het juiste.

Toen we de order-agent voor deze klant bouwden, was waar we steeds tegenaan liepen niet welke engine we moesten kiezen, maar hoe we de step-ledger klein genoeg hielden dat een junior engineer hem op vrijdagavond nog kon lezen. We hebben de ledger uiteindelijk teruggebracht tot flow-id, step-name, result en timestamp (vier kolommen), en al het andere naar normale applicatietabellen geduwd. De goedkoopste audit die je vijf minuten na het lezen van dit stuk op je eigen workflow-laag kunt doen: tel hoeveel kolommen jouw equivalent van die tabel heeft. Meer dan zes? Vraag je af waarom.

Kern

Voor een fulfilmentteam van 26 mensen met een Nederlandse bedrijfsgeheimenpositie won de saaie BullMQ + Postgres-outbox van Temporal en Inngest op kostprijs per zending en op vrijdagavondrealiteit.

FAQ

Wanneer wint Temporal van een zelfgebouwde outbox?

Als je team Temporal al ergens anders draait, als je een platformteam hebt dat het deelt over producten, of als lange sleeps zeldzaam zijn. De kosten van de action meter verdelen zich, en het spiergeheugen voor workflow-versioning verdient zich terug.

Is Temporal zelf hosten realistisch voor een bedrijf van 26 mensen?

Het kan, maar het kost veel aandacht. Je draait Postgres of Cassandra plus de Temporal-cluster plus workers, met version upgrades. Als het de juiste keuze is, reken dan minstens een kwart engineer in op ops — niet nul.

Hoe werkt de Wbb voor een event log in een vendor-cloud?

De wet eist redelijke maatregelen om het geheim te bewaren. Een vendor-cloud is niet automatisch uitgesloten, maar je moet toegang, encryptie en contractuele beperkingen documenteren. Lokale opslag maakt het auditverhaal eenvoudiger.

Wat is het goedkoopste dat we uit dit ontwerp kunnen overnemen?

De step recorder. Een flow_step-tabel met flow_id, step_name, result en recorded_at, plus een step()-wrapper die eerst checkt of er al een rij staat. Veertig regels. Geeft je at-most-once per flow.

ai agentsautomationworkflowarchitectureoperationse-commerce

Iets bouwen?

Start een project