Notifications

Every notification is triggered by a domain event written to the outbox in the same database transaction as the change it describes. Order, payment and delivery code never call the notification service directly, so a slow mail server can never roll back a paid order, and a crash can never lose the email for an order that did commit.

Channels and providers

ChannelProvider
EmailSMTP
PushFirebase Cloud Messaging (device tokens registered by the apps)
SMSTwilio — also carries sign-in codes and delivery codes
In-appNo provider: the stored notification is the delivery, shown in the account's notification list.

A channel with no configured provider leaves its notifications queued rather than dropped. A development "console" provider exists and is refused at start-up in production.

Rules that shape delivery

  • Quiet hours. Routine notices are held until the recipient's morning — delayed, never dropped. Urgent ones (a failed payment, a driver's next job, a chargeback) go out regardless.
  • Digests. Busy streams are batched: a seller with forty orders gets one "40 new orders", not forty pushes.
  • Language. Every template exists in English and Arabic. The locale comes from the device, then the user's profile, then English.
  • Preferences. Buyers can switch notifications on or off per event and channel.
  • Templates. Admins can override the wording of a template from the admin console (Message templates).

What is sent today

The catalogue (generated into docs/notifications.md) holds 33 rules for 32 events across six audiences. Sign-in and password-reset codes are sent by email as well.

Buyer

EventChannelsQuiet hoursDigestMessage
order.placedEMAIL · IN_APPsent anywayindividualOrder confirmed
order.paidEMAIL · IN_APPsent anywayindividualPayment received
order.payment_failedPUSH · EMAIL · IN_APPsent anywayindividualPayment problem
order.shippedPUSH · EMAIL · IN_APPheld till morningbatchedOrder is on its way
delivery.out_for_deliveryPUSH · IN_APPsent anywayindividualArriving soon, with ETA
delivery.otp_issuedSMS · IN_APPsent anywayindividualYour delivery code
delivery.completedPUSH · IN_APPheld till morningbatchedDelivered
delivery.failedPUSH · SMS · IN_APPsent anywayindividualWe could not deliver
order.cancelledEMAIL · IN_APPheld till morningindividualOrder was cancelled
refund.settledEMAIL · IN_APPheld till morningindividualRefund sent
ticket.repliedPUSH · EMAIL · IN_APPheld till morningindividualSupport replied

Seller

EventChannelsQuiet hoursDigestMessage
order.group_placedPUSH · EMAIL · IN_APPsent anywaybatchedNew order
order.accept_deadline_missedPUSH · EMAIL · IN_APPsent anywayindividualOrder past its acceptance deadline
payout.paidEMAIL · IN_APPheld till morningindividualPayout sent
review.publishedIN_APPheld till morningbatchedNew review
catalog.product_rejectedEMAIL · IN_APPheld till morningbatchedProduct was not approved

Driver

EventChannelsQuiet hoursDigestMessage
delivery.task_assignedPUSHsent anywayindividualNew job
delivery.task_reassignedPUSH · IN_APPsent anywayindividualJob reassigned
driver.earning_recordedIN_APPheld till morningbatchedYou earned an amount

Admin, finance, support

EventChannelsQuiet hoursDigestMessage
order.accept_deadline_missedEMAIL · IN_APPsent anywaybatchedUnanswered order
payment.chargeback_openedEMAIL · IN_APPsent anywayindividualChargeback opened
seller.application_submittedIN_APPheld till morningbatchedSellers awaiting approval
review.flaggedIN_APPheld till morningbatchedReviews need moderation
cod.settlement_discrepancyEMAIL · IN_APPsent anywaybatchedCOD settlement short
ticket.sla_breachedEMAIL · IN_APPsent anywayindividualSupport SLA breached
Catalogued but not yet emitted

These events have templates but nothing in the code raises them yet, so they are never sent: inventory.low_stock, message.received, ticket.opened, order.accept_deadline_near, ledger.reconciliation_failed, delivery.cash_limit_near.

Live tracking

A realtime channel (Socket.IO, namespace /realtime) lets a buyer subscribe to their own order and receive driver position updates, and lets dispatchers subscribe to the fleet. GET /tracking/orders/{orderNumber} returns the last known position and an ETA. The driver app does not send location pings yet, so in practice positions are not flowing.