Overview
Customer notifications are not a fixed list of hardcoded emails. They are entries in theOrderNotificationManifest — a container-bound registry, seeded with one code-level default, that a consuming application extends or replaces. Each entry is a single Laravel Notification class that can fire two ways:
- Automatically, when the order or one of its fulfilments enters one of the entry’s trigger states.
- Manually, composed and sent on demand — either from the admin’s “Notify customer” action, or by calling
$order->notifyCustomer()directly.
Lunar ships exactly one notification out of the box:
Lunar\Core\Notifications\OrderUpdate, a general-purpose “here is an update on your order” email, manual-only, that renders an optional free-text message. Branded, auto-triggered lifecycle notifications (an order confirmation, a “your parcel has shipped” email, a refund receipt) are not shipped by default — register your own against the manifest as described below.The notification manifest
OrderNotifications facade:
key— a unique identifier, used in$order->notifyCustomer()calls and admin dropdowns.notification— the notification class. It is constructed differently depending onscope(see below).label— the dropdown label shown in the admin; defaults to the key, translated through__().on— the list of state$names (apayment_status,fulfilment_status, or per-fulfilmentFulfilmentStatename, depending on scope) that fire it automatically. Leave empty for a manual-only notification.manual— whether it appears in the admin’s send list (and so can be resent by hand even when it also fires automatically).scope— aLunar\Core\Enums\NotificationScopecase,OrderorFulfilment, deciding what the notification is constructed with.
Scope
NotificationScope::Order notifications are constructed as new $class($order, $message) and sent through the order. NotificationScope::Fulfilment notifications are constructed with the fulfilment (new $class($fulfilment)) so they can read tracking and line details, but are still delivered through the order’s notification routing ($fulfilment->order->notify(...)). Automatic fulfilment-scoped sends use the same manifest — see Fulfilments.
Sending a notification manually
Lunar\Core\Contracts\Actions\Orders\NotifiesCustomer:
$notificationis a key registered on the manifest. An unknown key throwsLunar\Core\Exceptions\OrderActionException.$recipients, when empty, resolves to the order’sbillingAddress/shippingAddresscontact emails (deduplicated, blanks removed). If that resolves to nothing, the action throws.- Each recipient is sent the notification directly (bypassing the order’s own mail routing, since the recipient is already explicit), and an
email-notificationactivity log entry is recorded per recipient. Lunar\Core\Events\Orders\OrderCustomerNotifiedis dispatched once, with the order, the notification key, and the resolved recipients.
$message implements Lunar\Core\Contracts\Notifications\AcceptsCustomerMessage (a marker interface documenting the __construct(Order $order, ?string $message = null) shape notifyCustomer() relies on):
Mail routing defaults
For automatic sends (dispatched through$order->notify(...) or $fulfilment->order->notify(...), not the explicit-recipient path above), Order implements Laravel’s routeNotificationForMail() so notifications work without extra setup:
- If the notification implements
Lunar\Core\Contracts\ResolvesOrderMailRoute, itsmailRouteForOrder(Order $order): string|array|nullwins outright — use this when the recipient is not an order contact at all (an account email, an ops inbox). - Otherwise, if it implements
Lunar\Core\Contracts\RoutesToOrderContact, itsorderContactType(): 'billing'|'shipping'picks which contact to use — a per-fulfilment “your parcel has shipped” notification would route toshippingthis way. - Otherwise the default is the billing contact.
- Whichever contact is chosen, if it has no
contact_email, the other contact is used instead.
Admin
The order screen’s “Notify customer” action populates its notification dropdown fromOrderNotifications::sendable(), lets staff pick recipients from the order’s contacts (or add an extra address), and calls $order->notifyCustomer() — so a notification sent from the admin leaves the same activity-log trail as one sent through the API.