Expand description
Delivery of NotificationRequested rows: the consumer side of the notification contract.
A hook trigger’s send_email / notify and a workflow’s send_email / notify /
send_notification / send_sms never tell anyone anything inside the write’s transaction: they
stage a NotificationRequested row in the
module’s outbox, which commits with the write. This module is what reads those rows back out:
NotificationDeliveryis the one trait a notification module (mail, push, SMS) implements: deliver one request, and say whether it was delivered, should be tried again or can never be delivered.NotificationDispatcheris the outbox relay’s sink: it routesNotificationRequestedrecords to the registered delivery, claims each withinbox::onceso a redelivered row is not delivered twice, and maps the outcome onto the relay’s own retry and dead-letter semantics.RecordingDeliveryis a test double a notification module’s own tests, and the framework’s, drive the dispatcher with.
let dispatcher = Arc::new(NotificationDispatcher::new(pool.clone(), "hr", Arc::new(MailDelivery::new(..))));
// the relay: every staged event goes to the sink; a record that is not a request is accepted and ignored
runner::supervise(pool, RelayConfig::new("hr"), dispatcher.sink(), shutdown);§Guarantees
At least once, and one effect when the provider honours the id. The claim and the delivery
share one transaction: it is opened, the (consumer, event id) is claimed, the delivery runs, and
the transaction commits only when the delivery answered Delivered. A redelivery of a delivered
row finds the claim and does nothing; two relays handing the same row at once serialize on the
claim; a Retry or a Reject rolls the claim back, so the row is offered again or dead. What no
database can do is make a mail provider’s send atomic with a commit: a crash after the provider
accepted the message and before the commit delivers it again, which is why
PendingNotification::id is handed to the delivery as the idempotency key to give the provider.
Correlation and causation are preserved. The delivery runs inside the staging write’s
correlation id (so anything it writes or logs carries it), and PendingNotification carries the
record’s correlation id and causation id; PendingNotification::id is the id to cite as the
causation of anything the delivery stages in turn.
Retry is counted here, not in the table. The relay re-hands a pending row on every pass and
keeps no attempt count, so the dispatcher counts Retry results per
record and waits between them (RetryPolicy): within the backoff window a record is left
pending without calling the delivery, and after RetryPolicy::max_attempts the dispatcher
reports the record exhausted, which the relay marks dead (failed_at, failure_reason) and
relay::reemit hands back once the cause is fixed. The count and the wait live in this
process: a restart forgets them, and each instance of a service counts for itself. A Reject
is dead at once, with the delivery’s reason.
Structs§
- Notification
Dispatcher - The outbox relay’s sink for
NotificationRequestedrows: see the module docs. - Pending
Notification - One staged request, as a delivery sees it.
- Recording
Delivery - A
NotificationDeliveryfor tests: records every request it is handed and answers from a script. - Retry
Policy - How many times, and how far apart, a dispatcher offers a request that answers
DeliveryOutcome::Retry.
Enums§
- Delivery
Outcome - What a delivery says about one request.
Traits§
- Notification
Delivery - What a notification module implements: perform one request.
Type Aliases§
- Sink
Future - The future a relay sink returns.