---
id: WF-PFM-NOT-013
title: Record provider feedback
trigger: SES publishes delivery or engagement feedback.
product_area: platform
module: notifications
revision: 1
status: draft
design_intent: confirmed
origin: Andrew 2026-10-09 (tracking on for transactional mail; delivery feedback and suppression accepted),
  services-platform:docs/communications/architecture.md#2026-10-09 — Final acceptance
implementation_evidence: observed
inference: []
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
implements:
- CAP-PFM-NOT-019
- CAP-PFM-NOT-020
moves:
- STATE-PFM-NOT-012
- STATE-PFM-NOT-013
- STATE-PFM-NOT-011
crosses:
- SVC-PFM-NOT-communications
hops:
- n: 1
  actor_or_service: SES and EventBridge
  action: Forward both sending-lane provider events through EventBridge to the feedback queue.
  anchor: services-platform:modules/communications/aws/resources/communications-feedback.sesv2.ts
  seam: event
  on_failure: Use provider event delivery retry.
  left_inconsistent: Delivery-state updates can lag provider activity.
  observer: Feedback-lag monitoring.
  provenance: observed
  source: services-platform:modules/communications/aws/resources/communications-feedback.sesv2.ts
- n: 2
  actor_or_service: Feedback processor
  action: Resolve message and workspace tags, ignore unrelated untagged events and require a matching
    tagged message.
  anchor: services-platform:modules/communications/services/feedback/process-ses-event.ts:processSesEvent
  seam: queue
  on_failure: Retry missing tagged messages up to five receives, then dead-letter.
  left_inconsistent: Feedback remains unrecorded until processing or redrive succeeds.
  observer: Unmatched-event metric and dead-letter alarm.
  provenance: observed
  source: services-platform:modules/communications/services/feedback/process-ses-event.ts:processSesEvent
- n: 3
  actor_or_service: Feedback processor
  action: Deduplicate the derived event identity, apply outcome precedence and earliest times, and update
    engagement counts including likely-human signals.
  anchor: services-platform:modules/communications/services/persistence/messages.ts:recordMessageEvent
  seam: sync
  on_failure: Roll back event processing and retry.
  left_inconsistent: No partially committed event update.
  observer: Feedback, bounce and complaint metrics and message history.
  provenance: observed
  source: services-platform:modules/communications/services/persistence/messages.ts:recordMessageEvent
- n: 4
  actor_or_service: Feedback processor
  action: After event commit, protect permanently bounced or complaining addresses; complete unresolved
    submission from qualifying send or delivery feedback.
  anchor: services-platform:modules/communications/services/persistence/suppressions.ts:suppressAddress
  seam: sync
  on_failure: Retry the queue item after suppression failure; a duplicate event returns before repeating
    that suppression.
  left_inconsistent: The delivery event can remain recorded while address protection is missing.
  observer: Suppression history; no dedicated repair observer for this gap.
  provenance: test
  source: services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#suppresses
    the address for the tenant
idempotency: Derived event identity ignores duplicates; precedence and earliest timestamps tolerate reordering.
concurrency: Database event updates are atomic, but subsequent address suppression has a separate failure
  boundary.
branches:
- Copy-recipient events remain historical without changing primary-recipient delivery outcome.
- Untagged provider events are acknowledged as unrelated.
waits:
- Tagged messages not yet visible can be retried before dead-letter handling.
evidence:
- 'services-platform at ad3f6a65: modules/communications/services/feedback/process-ses-event.ts; aws/lambda/FEEDBACK.handler.ts;
  aws/resources/communications-feedback.sesv2.ts; docs/communications/architecture.md §8.'
controls: [TC-AVL-004, TC-AVL-005, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-006, TC-CRY-001, TC-CRY-002, TC-CRY-003, TC-CRY-004, TC-DAT-001, TC-DAT-002, TC-DAT-003, TC-DAT-004, TC-DAT-006, TC-IAM-001, TC-IAM-002, TC-IAM-004, TC-IAM-005, TC-IAM-006, TC-LOG-001, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-LOG-005, TC-MON-001, TC-MON-002, TC-PI-001, TC-PI-002, TC-PI-003, TC-PI-004, TC-PI-005, TC-PRV-001, TC-PRV-002, TC-PRV-004, TC-SEC-001, TC-SEC-002, TC-SEC-003, TC-SEC-004, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-VEN-001, TC-VEN-002, TC-VEN-003]
---
# Record provider feedback

## What It Does

SES publishes delivery or engagement feedback.

## The Flow

```mermaid
flowchart TD
    h0["Forward both sending-lane provider events through EventBridge to the feedback queue"]
    h1["Resolve message and workspace tags, ignore unrelated untagged events and require a matching tagged message"]
    h0 --> h1
    h2["Deduplicate the derived event identity, apply outcome precedence and earliest times, and update engagement counts i"]
    h1 --> h2
    h3["After event commit, protect permanently bounced or complaining addresses"]
    h2 --> h3
```

## When Something Fails

The hop rows identify each retry boundary, any state left behind and the available observer. Derived event identity ignores duplicates; precedence and earliest timestamps tolerate reordering. Database event updates are atomic, but subsequent address suppression has a separate failure boundary.

## Open Design Questions

Copy-recipient events remain historical without changing primary-recipient delivery outcome. Untagged provider events are acknowledged as unrelated. Tagged messages not yet visible can be retried before dead-letter handling.
