Skip to main content

Async processing

Two durable mechanisms and one that does not survive a crash.

MechanismProcessSurvives process crash
Prisma outbox, then EventEmitter2Dispatcher only on the workerYes. The row commits with the business write
Bull queueWorker processorsYes. Redis
EventEmitter2.emit inside the APIListeners loaded in that processNo

Do not replace an outbox enqueue with an in-process emit for order side effects. A listener registered only on the worker will not see an API emit, and the emit is not transactional.

Dispatcher​

OutboxDispatcherService:

BehaviorValue read
PollEvery 1 second
ClaimBatch 100, FOR UPDATE SKIP LOCKED
Dispatch concurrency10
SuccessStatus PROCESSED
Listener throwAttempts increment, stay PENDING until 10, then DEAD. lastError stored
Backoffmin(60_000, 2^attempts * 1000) ms
Stale PROCESSINGOlder than 5 minutes returns to PENDING without incrementing attempts
PurgePROCESSED older than 30 days, batches of 5000, max 20 batches. Cron 03:00
ProcessedEvent purgeOlder than 90 days, same batching

emitAsync waits for listeners. If any listener rejects, the promise rejects and the event stays retryable, so all listeners run again. A listener that already succeeded and is not idempotent will double-apply. IdempotentEventHandlerService exists and is opt-in. It locks the order row when orderId is provided and records ProcessedEvent. Which listeners call it was not fully enumerated.

DEAD rows are not automatically returned to PENDING. The replay procedure is unknown.

In-process events that are not outbox types​

NameProducerListener found
channel.createdChannelServicechannel location auto-bind
channel.updatedChannelServicewebhook health
audit.logaudit interceptor, WooCommerce webhook healthAuditLogListener
product.content.changedproduct event publisherProductEmbeddingListener
product.channel-allocation.changedproduct eventsProductStockSyncListener
shipment.status.changedshipment servicesfulfillment, NDR, return shipment
return.status.changedreturn commandnone
return.stock.restoredreturn inventorynone
No consumer

return.status.changed and return.stock.restored are emitted. No @OnEvent listener was found. Nothing retries them.

Details: Events and Queues.