SIGNALS Documentation
API Reference

Delivery Tracking Resolver Registry

Contract for registering inbound delivery-tracking payload resolvers that map provider webhooks onto communication_log updates.

Overview

App\Services\Notifications\DeliveryTrackingResolverRegistry is the inbound delivery-tracking seam (P7B-12). It maps a provider key to an App\Contracts\Notifications\DeliveryTrackingResolver, then lets the signed inbound webhook endpoint resolve provider payloads into DeliveryTrackingUpdateData for queued application against communication_log.

Core ships no production resolvers. A FakeDeliveryTrackingResolver (provider() → fake) exists for tests and local fixtures only. Plugins register resolvers through PluginRegistrar::deliveryTrackingResolver().

Public surface

Method Purpose
register() Warehouse one resolver instance under its provider() key
get() Return the resolver for a provider or throw if it is missing
has() Check whether a provider key is registered
providers() Return all registered provider keys, sorted alphabetically

Accepted contract and identity

Every resolver implements App\Contracts\Notifications\DeliveryTrackingResolver:

public function provider(): string;
public function resolve(array $payload): ?DeliveryTrackingUpdateData;

provider() is the stable registry identity and the inbound URL segment {provider} consumed by NotificationInboundController.

resolve() maps a provider-specific payload array to App\Data\Notifications\DeliveryTrackingUpdateData, or returns null when the payload is unrecognised.

Current core registrations

Core does not seed production providers into this registry. The only reference implementation in the codebase is App\Services\Notifications\Resolvers\FakeDeliveryTrackingResolver for tests:

Provider Class Notes
fake FakeDeliveryTrackingResolver Expects status plus communication_log_id or message_id; optional occurred_at and meta

Ordering and lookup behavior

providers() returns registered provider keys sorted alphabetically. This is a catalogue/discovery order only.

register() stores resolvers in an associative array keyed by provider(). Registering the same provider again replaces the earlier resolver instance. The registry does not reject duplicates.

get($provider) throws InvalidArgumentException with Unknown delivery tracking provider [{$provider}]. when the provider is missing.

has($provider) is the optional guard used by the inbound controller before signature verification.

Runtime consumption

App\Http\Controllers\Api\V1\NotificationInboundController is the primary consumer:

  1. Abort 404 when has($provider) is false
  2. Verify the signed inbound request using per-provider secrets from settings (notifications.inbound_{provider}_secret, with shared header/tolerance fallbacks)
  3. Call get($provider)->resolve($payload)
  4. Return 422 with Unrecognised payload. when resolve returns null
  5. Dispatch ApplyDeliveryTrackingUpdate on the notifications queue and return 202 { "status": "accepted" }

Auth is signature-only (no Sanctum) on that endpoint.

Worked example

This is the current registration and lookup shape exercised in tests:

$registry = app(DeliveryTrackingResolverRegistry::class);
$registry->register(new FakeDeliveryTrackingResolver);

$registry->has('fake'); // true
$registry->providers(); // ['fake']

$update = $registry->get('fake')->resolve([
    'communication_log_id' => 1,
    'status' => 'delivered',
    'occurred_at' => '2026-07-16T12:00:00Z',
    'meta' => ['reason' => 'smtp-250'],
]);

An unknown provider fails get() with InvalidArgumentException. An unrecognised payload returns null from resolve() and is rejected by the inbound controller with HTTP 422.