---
id: WF-PFM-NOT-011
title: Accept and send association e-mail
trigger: A lane worker receives an association communication request.
product_area: platform
module: notifications
revision: 1
status: draft
design_intent: confirmed
origin: Andrew 2026-10-10 (SQS owns the retries; acceptance behind the queue; redrive retries exhausted
  e-mails), services-platform:docs/communications/architecture.md#18.7 The dead-letter queue, and what
  a redrive does
implementation_evidence: observed
inference: []
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
implements:
- CAP-PFM-NOT-017
- CAP-PFM-NOT-018
- CAP-PFM-NOT-020
- CAP-PFM-NOT-016
moves:
- STATE-PFM-NOT-010
- STATE-PFM-NOT-011
crosses:
- SVC-PFM-NOT-communications
hops:
- n: 1
  actor_or_service: Send consumer
  action: For a request without an outcome, load and match stored content to its pointer, validate it
    and resolve the required template version or caller content.
  anchor: services-platform:modules/communications/services/requests/accept-communication-request.ts:acceptCommunicationRequest
  seam: queue
  on_failure: Record invalid or configuration-blocked outcomes with reasons.
  left_inconsistent: No partially accepted request.
  observer: Request outcome and configuration reasons.
  provenance: test
  source: services-platform:modules/communications/tests/e2e/requests.e2e.spec.js#blocks a request that
    names no template, recording the reason staff can act on
- n: 2
  actor_or_service: Send consumer
  action: Render recipient content with association and message references, validate copies and protection
    rules, and store bodies outside the transaction.
  anchor: services-platform:modules/communications/services/templates/render-message.ts
  seam: sync
  on_failure: Retry processing; deterministic storage keys replace earlier writes.
  left_inconsistent: Rendered objects can remain without database references.
  observer: No dedicated orphan observer.
  provenance: documented
  source: services-platform:docs/communications/architecture.md#18.1 The flow
- n: 3
  actor_or_service: Send consumer
  action: Commit the request outcome and version-pinned draft or withheld messages together.
  anchor: services-platform:modules/communications/services/persistence/messages.ts:insertAcceptedMessages
  seam: sync
  on_failure: Roll back database acceptance and retry.
  left_inconsistent: Stored bodies can remain while database rows are absent.
  observer: Request and message History.
  provenance: test
  source: services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#accepts
    the request with the owner’s published version pinned
- n: 4
  actor_or_service: Send consumer
  action: For each draft, obtain lane capacity, claim with current protection and expiry checks, open
    an attempt and select the available sender.
  anchor: services-platform:modules/communications/services/persistence/messages.ts:claimMessageForSubmission
  seam: sync
  on_failure: Requeue capacity-held work without consuming receives; settle expired or protected messages
    without an attempt.
  left_inconsistent: Unclaimed drafts remain waiting.
  observer: Lane-capacity metric.
  provenance: observed
  source: services-platform:modules/communications/services/dispatch/process-communication-request.ts:processCommunicationRequest
- n: 5
  actor_or_service: Send consumer and SES
  action: Call SES outside the transaction with lane configuration, correlation and Marketing unsubscribe
    data; persist acceptance or classify failure.
  anchor: services-platform:modules/communications/services/dispatch/dispatch-messages.ts:sendMessage
  seam: sync
  on_failure: Back off temporary failure; on the final receive fail attempted messages, while time-limited
    unattempted drafts may still dead-letter.
  left_inconsistent: A crash after acceptance can leave submitting unresolved; another claim after fifteen
    minutes can send a duplicate.
  observer: Attempt history and send/dead-letter alarms.
  provenance: observed
  source: services-platform:modules/communications/services/persistence/messages.ts:recordMessageSubmissionFailed
idempotency: An existing request outcome bypasses rendering and resumes stored messages; guarded claims
  prevent concurrent live sends.
concurrency: One queue item is handled per invocation; sending ambiguity permits another claim after fifteen
  minutes.
branches:
- Protection can withhold every recipient.
- The default-on exhausted-message retry permits eligible first-receive redrive.
waits:
- Lane capacity and retry backoff can defer the remaining draft messages.
evidence:
- 'services-platform at ad3f6a65: modules/communications/services/dispatch/{process-communication-request,dispatch-messages,lane-budget}.ts;
  services/requests/accept-communication-request.ts; services/persistence/messages.ts; services/templates/{render-message,resolution,test-send}.ts;
  aws/lambda/{SEND,SEND_MARKETING}.handler.ts.'
controls: [TC-AVL-001, 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-TEN-004, TC-VEN-001, TC-VEN-002, TC-VEN-003]
---
# Accept and send association e-mail

## What It Does

A lane worker receives an association communication request.

## The Flow

```mermaid
flowchart TD
    h0["For a request without an outcome, load and match stored content to its pointer, validate it and resolve the require"]
    h1["Render recipient content with association and message references, validate copies and protection rules, and store b"]
    h0 --> h1
    h2["Commit the request outcome and version-pinned draft or withheld messages together"]
    h1 --> h2
    h3["For each draft, obtain lane capacity, claim with current protection and expiry checks, open an attempt and select t"]
    h2 --> h3
    h4["Call SES outside the transaction with lane configuration, correlation and Marketing unsubscribe data"]
    h3 --> h4
```

## When Something Fails

The hop rows identify each retry boundary, any state left behind and the available observer. An existing request outcome bypasses rendering and resumes stored messages; guarded claims prevent concurrent live sends. One queue item is handled per invocation; sending ambiguity permits another claim after fifteen minutes.

## Open Design Questions

Protection can withhold every recipient. The default-on exhausted-message retry permits eligible first-receive redrive. Lane capacity and retry backoff can defer the remaining draft messages.
