The delivery proof
Every pushed change notice writes an append-only legal_notices row (unless you turn
durable_medium.proof off): who was informed, when, on which medium, the exact text sent, its
hash, and whether the notice carried its mandatory content.
That is the evidence a durable-medium notice actually went out (CJEU C-375/15) — audit it like any model:
The row is written by a listener on NotificationSent, so it exists because the mail channel
accepted the message — not because the sweep reached the subject in its own loop. Before 0.19.0 it
was written at enqueue time, which meant a dead worker or a refusing transport produced a green
run and a proof row for a notice nobody received. If your ledger predates 0.19.0, rows from before
the upgrade attest that the notice was queued.
It is written only when the mail channel hands back the message it sent. The channel sends
nothing to a subject without a mail address, or when a MessageSending listener cancels the message,
and fires NotificationSent all the same. No row is written then: the attempt counts as failed, the
subject is tried again, and after notifications.max_attempts it is reported as unreachable. Without a
row, a deemed-consent window never closes on that subject.
A transport that refuses the message throws, Laravel reports that as NotificationFailed, and the
attempt counts as failed in the same way. On a synchronous queue the exception surfaces inside
legal-consent:dispatch-notices itself. There it ends that subject's notice on that channel and
nothing more: the subject's other channels are still sent, and the sweep goes on with the next
subject.
Any other error ends the run, a queue that cannot be reached for example. The sweep records a
chunk's notices as on their way before it hands them over, and the records of the notices it had not
handed over by then go back to what they were, so the next run serves those subjects rather than
waiting out notifications.requeue_after_minutes and counting an attempt that never happened.
It does not attest receipt. NotificationSent means the mailer took the message; there is
deliberately no delivered_at, because a column claiming more than the package can observe would be
worse than no column.
use Pushery\LegalConsent\Models\LegalNotice;
$proof = LegalNotice::query()->where('document_key', 'terms')->latest('sent_at')->get();
$deficient = LegalNotice::query()->where('mandatory_content_ok', false)->exists(); // should be false
Retention
The proof is append-only and holds personal data, so legal-consent:prune covers it on the
same retention rule as the consent ledger: superseded and orphaned rows past the period go,
while the newest notice per subject and document is kept — it is what proves the change
behind their current standing was lawfully announced.
The prune sweep is off by default. See Retention.