Skip to main content

Sample: OrderService — In-Process Outbox with Scheduled Delivery

Location: samples/outbox-inapp/OrderService.InAppOutboxScheduling/
Framework: ASP.NET Core 9 Minimal API
Transport: RabbitMQ — Hermodr.Publisher.RabbitMq
Pattern: Transactional Outbox with delayed transport delivery


Overview

This sample demonstrates a specific semantic: the domain event already occurred, but its broker delivery is deferred.

The API creates an Order immediately and publishes OrderCreated through the outbox using OutboxPublishOptions.ScheduleDeliveryAt. The outbox row is persisted immediately, and the in-process relay waits until the scheduled UTC timestamp before forwarding the event to RabbitMQ.

POST /orders
-> OrderCreated recorded now
-> DbOutboxMessage persisted now
-> relay forwards later when ScheduleDeliveryAt is due

What it shows

1. Delivery scheduling via Outbox

Business code maps request input to outbox publish options:

var publishOptions = request.ScheduleEventDeliveryAt is { } scheduleDeliveryAt
? new OutboxPublishOptions { ScheduleDeliveryAt = scheduleDeliveryAt }
: null;

await _publisher.PublishAsync(new OrderCreated
{
OrderId = order.Id,
CustomerId = order.CustomerId,
TotalAmount = order.TotalAmount,
CreatedAt = order.CreatedAt,
Items = /* ... */
}, publishOptions, ct);

2. Immediate persistence, delayed forwarding

The outbox stores the event row immediately, setting NextRetryAt to the scheduled delivery time. The relay then treats the row as ineligible until it becomes due.

3. Inspectable outbox state

The sample exposes:

GET /outbox/messages

so you can observe Pending, NextRetryAt, and later Delivered without opening SQLite manually.


Running the sample

cd samples/outbox-inapp/OrderService.InAppOutboxScheduling

docker compose up -d

dotnet run --project OrderService.InAppOutboxScheduling.csproj

Generate a timestamp a few seconds in the future:

SCHEDULE_AT=$(python3 - <<'PY'
from datetime import datetime, timezone, timedelta
print((datetime.now(timezone.utc) + timedelta(seconds=20)).isoformat().replace('+00:00', 'Z'))
PY
)

Create a scheduled-delivery order event:

curl -s -X POST http://localhost:5000/orders \
-H "Content-Type: application/json" \
-d "{
\"customerId\": \"cust-42\",
\"scheduleEventDeliveryAt\": \"$SCHEDULE_AT\",
\"items\": [
{ \"productId\": \"sku-001\", \"productName\": \"Widget\", \"quantity\": 2, \"unitPrice\": 9.99 }
]
}" | jq

Inspect the outbox immediately:

curl -s http://localhost:5000/outbox/messages | jq

Wait until due time, then inspect again:

sleep 25
curl -s http://localhost:5000/outbox/messages | jq

Why this sample exists

This sample is intentionally different from “future domain events”. It models:

  • event occurred now
  • outbox record exists now
  • transport delivery happens later

That keeps the event semantics intact while still supporting infrastructure-level delayed fan-out.