Skip to content

feat: expose notification delivery status timeline - #889

Open
larryjay007 wants to merge 5 commits into
Pulsefy:mainfrom
larryjay007:feat/notification-delivery-status-timeline
Open

feat: expose notification delivery status timeline#889
larryjay007 wants to merge 5 commits into
Pulsefy:mainfrom
larryjay007:feat/notification-delivery-status-timeline

Conversation

@larryjay007

Copy link
Copy Markdown
Contributor

What

Adds a real delivery-attempt timeline for notifications, addressing the gap that
NotificationOutbox only ever holds the latest attempt (lastAttemptAt/lastError get
overwritten on every retry) — there was previously no way to see attempt 1 vs attempt 2 vs
attempt 3 for debugging a failed notification flow.

Schema

  • New DeliveryAttemptOutcome enum (success | failed) and NotificationDeliveryAttempt
    model, related to the existing NotificationOutbox.
  • Migration verified for real: built a throwaway SQLite DB from the actual migration history
    via prisma migrate dev, then hand-isolated the generated SQL to just our table. The
    auto-diff also included three unrelated tables (ArtifactAccessToken, ImportJob,
    SorobanEventCorrelation) that exist in schema.prisma but have no migration backing
    them — pre-existing drift, unrelated to this change, excluded from this migration.

Failure categorization

Added notification-failure-classifier.ts, bucketing raw errors into a small fixed set
(timeout, rate_limited, invalid_recipient, provider_error, unknown) rather than
passing raw error text as a metric label. This deliberately diverges from the existing
incrementCallbackFailure/incrementTxSubmissionFailure pattern elsewhere in the codebase,
which uses raw (truncated) error text as a Prometheus label — a cardinality-explosion risk.

Processor

onCompleted/onFailed now also insert a NotificationDeliveryAttempt row and record the
new metrics, alongside the existing NotificationOutbox status update (which remains the
source of truth for "current state"; the new table is the history).

Metrics

Two new counters: notification_delivery_attempts_total (labels: type, outcome) and
notification_delivery_failures_by_category_total (labels: type, failure_category).

Admin endpoints

Both added to the existing OutboxController, matching its ApiKeyGuard/RolesGuard/
@Roles(admin, operator) pattern:

  • GET /notifications/outbox/:id/attempts — full timeline for one record.
  • GET /notifications/outbox/delivery-attempts — filtered, paginated history
    (outcome, failureCategory, type, from, to, limit, offset).

Note: the filtered endpoint is declared before the existing @Get(':id') route in the
file, since NestJS/Express match routes in declaration order — :id would otherwise
silently capture "delivery-attempts" as an id value and make the new route unreachable.

Testing

npx tsc --noEmit is clean for every file this PR touches. 13 pre-existing TypeScript
errors remain elsewhere in the repo (artifact-token/upload-session spec files, tied to
the same ArtifactAccessToken schema drift noted above) — confirmed via git stash to
predate this change and be unrelated.

Known gap, flagging honestly rather than overclaiming: no automated tests were added yet
for the new code paths (classifier, processor changes, service methods, controller
endpoints). Recommend this as a fast follow-up before merge, or happy to add them if there's
time before review.

Closes #716

Adds a NotificationDeliveryAttempt table, recording every individual
delivery attempt (not just the latest, unlike NotificationOutbox's
lastAttemptAt/lastError which get overwritten on each retry).

- Prisma: DeliveryAttemptOutcome enum + NotificationDeliveryAttempt model,
  relation to NotificationOutbox. Migration hand-verified against a real
  throwaway SQLite DB built from the actual migration history (via
  prisma migrate dev), then isolated to just our table — the auto-diff
  also bundled in three unrelated, already-drifted tables
  (ArtifactAccessToken, ImportJob, SorobanEventCorrelation) that exist in
  schema.prisma but have no migration; excluded those, not ours to fix.
- notification-failure-classifier.ts: buckets raw errors into a small
  fixed category set (timeout/rate_limited/invalid_recipient/
  provider_error/unknown), specifically to avoid the cardinality-explosion
  pattern already present in incrementCallbackFailure/
  incrementTxSubmissionFailure (raw error text as a Prometheus label).
- notifications.processor.ts: onCompleted/onFailed now also insert a
  NotificationDeliveryAttempt row and record the new metrics, alongside
  the existing outbox status update.
- metrics.providers.ts/metrics.service.ts: two new counters,
  notification_delivery_attempts_total (type, outcome) and
  notification_delivery_failures_by_category_total (type,
  failure_category) — deliberately separate from the existing
  callback_failures_total pattern rather than reusing it.
- outbox.controller.ts: GET /notifications/outbox/:id/attempts (full
  timeline for one record) and GET /notifications/outbox/delivery-attempts
  (filtered, paginated history across all records — outcome,
  failureCategory, type, from/to, limit/offset). Both admin/operator-role
  gated, matching the existing controller's guard pattern. The filtered
  endpoint is deliberately declared before the existing @get(':id') route
  in file order, since NestJS/Express match routes in declaration order
  and :id would otherwise capture 'delivery-attempts' as an id value.

Verified: npx tsc --noEmit clean for all touched files (13 pre-existing,
unrelated errors elsewhere in the repo, confirmed via git stash to
predate this change).

Known gap: no automated tests added yet for the new code paths
(classifier, processor changes, service methods, controller endpoints).
Recommend as immediate follow-up before merge if possible.

Closes Pulsefy#716
@drips-wave

drips-wave Bot commented Jul 29, 2026

Copy link
Copy Markdown

@larryjay007 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Cedarich

Copy link
Copy Markdown
Contributor

Kindly fix workflow

@Cedarich

Copy link
Copy Markdown
Contributor

@larryjay007

@larryjay007

Copy link
Copy Markdown
Contributor Author

@Cedarich

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expose Notification Delivery Status Timeline

2 participants