Tooling
Workflow runtime choice: Temporal vs Inngest vs BullMQ
It is 21:00 on a Sunday. The weekly client report for your biggest retainer did not ship. You open the queue dashboard and stare at a row you cannot read.

The 9pm Sunday question
It is a Sunday evening, just past 21:00. The weekly performance report for your largest retainer did not go out. The Slack channel where it should appear sits empty. You open the queue dashboard and see one row that reads processing, with a spinner that has been spinning for forty minutes.
You do not know if the workflow is stuck, if it crashed silently, if the Meta Ads API rate-limited the export step, or if Looker Studio is down. You are not a backend engineer. You are the head of operations. The backend engineer who built this lives in Rotterdam and is on a flight to Lisbon.
By the time anyone reads the actual logs, the client wakes up on Monday without their report. This is the moment that decides which workflow runtime an agency should pick. Not the benchmark. Not the marketing page. This moment, at 21:00, with no engineer available.
Three contenders for the reporting layer
For a Dutch agency running between 200,000 and 350,000 workflow executions a month (weekly client reports, monthly invoice runs, daily pulls from ad platforms), three options dominate the conversation in 2026.
- Temporal, a durable execution engine with a worker pool. You write workflows as code, Temporal handles retries, timers, and state replay.
- Inngest, an event-driven workflow platform with a managed control plane while your functions run on your own infrastructure.
- BullMQ on Redis Streams, a Node-native queue you run yourself, with all the glue code written by your team.
All three can run the same reporting workflow. The choice is not capability. The choice is what breaks first, who notices, and how long it takes to find the broken step when the engineer is unreachable.
Per-run cost at 280k executions a month
Cost is the axis everyone scores first because it is the easiest to compute. Here is a back-of-envelope estimate for a sub-€15M agency at roughly 280,000 executions a month, assuming an average reporting workflow has five to seven steps. Numbers below cover vendor charges plus compute on the agency's own cloud account.
Runtime Vendor /mo Compute /mo Total /mo Per run
Temporal Cloud ~€180 ~€80 ~€260 €0.00093
Inngest (Team tier) ~€70 ~€40 ~€110 €0.00039
BullMQ + Redis (self) €0 ~€90 ~€90 €0.00032
The spread looks dramatic in percentages and trivial in euros. The cheapest and the most expensive option differ by roughly €170 a month. That is two engineer-hours at a normal Dutch agency rate. If the cheaper runtime costs you three engineer-hours per incident, you are losing money before you ship anything.
Vendor pricing pages change quarterly. Re-run this table against the current published tiers before you commit. Treat the numbers above as a shape, not a quote.
Incident paging defaults
The second axis is incident routing. Each runtime has a different default for what happens when a workflow misses its SLA.
With BullMQ, you wrote the SLA check. If you did not, there is no check. If you did, it pages whoever you wired up. That is the honest answer: you get exactly what you built, and the on-call rotation is whichever engineer happens to read Slack first.
Temporal ships with workflow and activity timeouts as first-class primitives. You can declare that a weekly_report workflow must complete within six hours. After that, Temporal raises a timeout event in the workflow history. You still need to forward that event to PagerDuty or OpsGenie, but the detection is automatic and the visibility API lets you list every stalled run with one query.
Inngest is the closest to fires a webhook into your incident channel by default. Step-level failures appear in the dashboard with a stack trace, and the platform can post to Slack or call a function when a run exceeds its declared duration without any custom wiring.
Readability for the ops lead
This is the axis nobody benchmarks. It is also the axis that quietly costs the most engineer hours over a year.
Open the Inngest dashboard at the moment of failure. You see a vertical timeline. Step one, fetch_meta_ads_data, green check, 240ms. Step two, fetch_google_ads_data, red X, ETIMEDOUT after 30s. An ops lead can read that. They can click into the failed step and see Google Ads API returned 502. They know to retry the report on Monday morning. No engineer needed.
Open Temporal's UI for the same failure. You see an event history with names like WorkflowTaskCompleted, ActivityTaskScheduled, ActivityTaskFailed. The information is there, but the vocabulary is engineering vocabulary. An ops lead who has not been onboarded will not translate it without help.
BullMQ's bundled UI, or the popular Bull Board, shows queue position, attempts remaining, and the error string if you logged one. It is the most spartan of the three. Whether the ops lead can read it depends entirely on what your engineer chose to log on that particular Wednesday in March when they wrote the worker. The BullMQ documentation is clear about this: observability is your problem.
The scoring rubric we actually use
Here is the scorecard we walk through with every agency client before committing to a runtime. Score one to five on each axis, multiply by the weight, sum the totals.
axes:
- name: per_run_cost_at_target_volume
weight: 2
note: "Vendor + compute at projected monthly executions"
- name: ops_lead_can_read_trace_unaided
weight: 4
note: "Sunday 21:00 test: can a non-engineer find the broken step?"
- name: incident_paging_out_of_box
weight: 3
note: "Does a stalled workflow page someone without custom code?"
- name: setup_cost_in_engineer_days
weight: 2
note: "First production workflow shipped end to end"
- name: lock_in_risk
weight: 1
note: "How many days to migrate off if pricing changes?"
Notice the weights. Cost is a two. Readability is a four. That is the explicit position: at this volume, the readability axis dominates the math because every minute the ops lead cannot self-serve a debug is a minute billed against an engineer.
If your ops lead cannot read the failure trace without paging an engineer, you picked the wrong runtime. Cost is a tiebreaker, not the axis.
Strongest fit by scenario
Run the scorecard honestly and one of three pictures emerges.
Inngest wins when an ops lead already runs the reporting process by hand and knows what each step is supposed to produce. The timeline view lets that person keep debugging without paging the engineer. The vendor cost is the highest of the cheap options, but the headcount math swings in its favour fast.
Temporal wins when workflows are longer, durations stretch into days (multi-day waits for client approval, slow-rolling content pipelines), or when an audit trail matters for compliance. Temporal's replayable execution model and rich event history are not matched by the other two. It is also the right pick if you already have a backend team that wants the control. It is not the right pick for the 9pm Sunday scenario this post opens with.
BullMQ wins for agencies under roughly 100,000 executions a month, with an engineer who actively wants to own the queue layer and is willing to write the paging, the timeouts, and the dashboard. The maintenance cost lives permanently in your team's calendar.
The five-minute screenshot test
Pull up the dashboard of whichever runtime you are leaning toward. Screenshot the screen the dashboard shows the moment a workflow fails. Send the screenshot to your ops lead. Ask one question: can you tell me which step broke and what to do about it, with no help from me?
If the answer is no, you have your data. Score the rubric and pick again.
When we built the client-reporting layer for an Amsterdam paid-media agency earlier this year, the thing we ran into was that their ops lead had been hand-running the Sunday process for two years and knew the workflow better than anyone on the engineering side. The win was not the engine. The win was process automation that surfaced step-level failure in language she already used.
Key takeaway
If your ops lead cannot read the failure trace without paging an engineer, you picked the wrong runtime. Cost is a tiebreaker, not the axis.
FAQ
How do I estimate per-run cost before committing to a vendor?
Take your last 30 days of executions, multiply by your projected steps per workflow, and plug the totals into each vendor's published tier. Add roughly 30% headroom for retries and replays.
Can I migrate from BullMQ to Temporal or Inngest later?
Yes, but expect to rewrite the workflow definitions. Queues are job-shaped; durable workflows are state-machine-shaped. Budget two to four engineer weeks per major workflow you move.
What if our ops lead does not want to debug workflows at all?
Then your weighting changes. Drop the readability axis to a two and push incident paging to a five, so the runtime that auto-routes failures to the right engineer wins on the scorecard.
Does self-hosted Temporal change the cost math?
It removes the vendor line and adds roughly half a day of platform-engineer time per month for upgrades, monitoring, and Cassandra or Postgres tuning. Worth it above ~500k executions a month, rarely worth it below.