---
id: CAP-PFM-NOT-017
title: Submit association communication requests
feature: FEAT-PFM-NOT-07
product_area: platform
module: notifications
revision: 1
status: draft
product_intent: proposed
implementation_evidence: observed
supersedes: []
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - >-
    services-platform:docs/communications/architecture.md#15. Decisions for
    Andrew
  - >-
    services-platform:docs/communications/architecture.md#2026-10-09 — Final
    acceptance
  - >-
    services-platform:docs/communications/architecture.md#18. Rework 2026-10-10:
    queue pointers, S3 for request data and bodies, SQS owns retries
  - >-
    services-platform:docs/communications/architecture.md#18. Rework 2026-10-10:
    queue pointers, S3 for request data and bodies, SQS owns retries
purpose: >-
  Applications submit association mail with durable request identity and
  inspectable acceptance outcomes.
outcomes:
  - >-
    Valid communication requests create recipient messages and retained request
    content.
  - Rendering mode and version constraints reject inconsistent stored requests.
concepts:
  - CON-PFM-NOT-005
  - CON-PFM-NOT-004
actors:
  - ACT-PFM-030
  - ACT-PFM-031
surfaces:
  - api
  - job
preconditions: []
postconditions: []
permissions:
  - key: platform.communications.request
    where_checked: api
    scope: tenant
rules:
  - id: BR-CAP-PFM-NOT-017-01
    statement: >-
      A submitting macroservice stores the request object and then inserts its
      pointer using the communications capability role within its business
      transaction; only a committed pointer makes the request available for
      dispatch.
    kind: invariant
    enforced_at:
      - database
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/db/communication-request-submissions.table.integration.spec.js#leaves
      no queue row behind when the caller’s transaction rolls back
    origin: >-
      services-platform:docs/communications/architecture.md#18.12 One S3 write
      inside the caller's transaction
  - id: BR-CAP-PFM-NOT-017-02
    statement: >-
      The capability role inserts caller-owned pointer fields within its
      workspace only; Claim and ordering fields, existing queue entries,
      messages and templates are inaccessible.
    kind: permission
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/db/communication-request-submissions.table.integration.spec.js#cannot
      read back even the rows it wrote
    origin: none
  - id: BR-CAP-PFM-NOT-017-03
    statement: >-
      Workspace, submitting service and idempotency key derive the request
      identifier; Repetition returns a duplicate receipt and preserves one
      entry, including handoff gaps; lost queue entries can be restored without
      changing request identity.
    kind: invariant
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/requests/submit-request.integration.spec.js#is
      still answered duplicate, because the request object says the id was
      submitted before
    origin: none
  - id: BR-CAP-PFM-NOT-017-04
    statement: >-
      Submission checks workspace, purpose, key, submitter, recipient shape,
      Marketing fields and caller-rendered content; invalid envelopes write
      nothing and do not consume their keys.
    kind: validation
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/communications/tests/e2e/requests.e2e.spec.js#takes
      nothing for that request, so its key is still free
    origin: none
  - id: BR-CAP-PFM-NOT-017-05
    statement: >-
      Queued processing records acceptance, suppression, invalid fields or
      configuration-blocking reasons, including missing, archived or unpublished
      templates, incompatible versions and render failures, for later
      inspection.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    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
    origin: none
  - id: BR-CAP-PFM-NOT-017-06
    statement: >-
      Acceptance pins a published version and renders each recipient once;
      Derived message identifiers represent either queued delivery or safeguard
      withholding.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    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
    origin: none
  - id: BR-CAP-PFM-NOT-017-07
    statement: >-
      Processing verifies the request object's workspace and request identity
      against its pointer; missing and mismatched objects become invalid rather
      than sending.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/storage/communication-objects.integration.spec.js#refuses
      an object that names another tenant, which is what the grant cannot
      prevent
    origin: none
  - id: BR-CAP-PFM-NOT-017-08
    statement: >-
      Platform injects association name and message reference into every
      rendering, replacing caller values for those reserved inputs.
    kind: invariant
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/communication-reads.integration.spec.js#sets
      the association over whatever the caller sent under that name, so a caller
      cannot rename the association
    origin: none
  - id: BR-CAP-PFM-NOT-017-09
    statement: >-
      Caller-rendered purposes accept the supplied subject and HTML unchanged,
      derive absent text and allow bounded attachments without templates;
      Platform-rendered purposes reject supplied content and attachments.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/dispatch/caller-rendered.integration.spec.js#derives
      the text part from the HTML when the caller sent none
    origin: none
  - id: BR-CAP-PFM-NOT-017-10
    statement: >-
      Optional sender display name, reply-to and copies never let the caller
      choose the sending address; Display names cannot be addresses; recipient
      safeguards check copies and remove copies matching the primary recipient.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/dispatch/caller-rendered.integration.spec.js#sends
      from the tenant's sender address, under the caller's display name
    origin: none
  - id: BR-CAP-PFM-NOT-017-11
    statement: >-
      Marketing sends render the specifically pinned message version, including
      older published versions; missing versions, ownership or purpose
      mismatches and newer unsupported contracts block sending.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/dispatch/owned-template.integration.spec.js#still
      sends a version that is no longer its template’s current one, and sends
      the pinned content
    origin: none
  - id: BR-CAP-PFM-NOT-017-12
    statement: >-
      Transactional compliance mail ignores a contact's general e-mail opt-out,
      while address suppression still withholds delivery.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: documented
    source: >-
      services-platform:docs/communications/architecture.md#15. Decisions for
      Andrew
    origin: >-
      services-platform:docs/communications/architecture.md#15. Decisions for
      Andrew
  - id: BR-CAP-PFM-NOT-017-13
    statement: >-
      IAM also provides readiness, owned-template access, tests, previews and
      changed-message feeds; missing actors and unsupported operations are
      refused with the supported-operation list.
    kind: validation
    enforced_at:
      - service
    must_prove: false
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/services/requests/handle-invocation.integration.spec.js#refuses
      an operation it does not have, naming the ones it does
    origin: none
  - id: BR-CAP-PFM-NOT-017-14
    statement: >-
      HTTP request submission requires a system caller and communication-request
      permission; console callers are rejected.
    kind: permission
    enforced_at:
      - api
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/communications/tests/e2e/requests.e2e.spec.js#refuses
      a console caller the system route that asks for a communication
    origin: none
  - id: BR-CAP-PFM-NOT-017-15
    statement: >-
      Request-object storage precedes database commit; a rollback can therefore
      leave an object without a queue pointer until storage lifecycle cleanup.
    kind: invariant
    enforced_at:
      - service
    must_prove: true
    provenance: inferred
    source: >-
      services-platform:docs/communications/architecture.md#18.12 One S3 write
      inside the caller's transaction
    origin: none
    note: >-
      The object write is not reversed by a later database rollback; the
      request-object lifecycle then bounds the orphan lifetime.
  - id: BR-CAP-PFM-NOT-017-16
    statement: >-
      Message persistence accepts Platform or caller rendering only;
      Platform-rendered non-test messages require a template version; draft test
      messages may omit it; caller-rendered messages cannot name a template
      version.
    kind: validation
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/db/communication-messages.table.integration.spec.js#refuses
      a transactional message Platform rendered, by the version check’s name
    origin: none
scenarios:
  - id: SC-CAP-PFM-NOT-017-01
    given: >-
      CRM submits compliance mail for a source record whose organization type
      has no template.
    when: Queued acceptance evaluates its configuration.
    then: >-
      No message sends; the request records missing-template blockage with
      purpose and owner information.
    provenance: test
    source: >-
      services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js#stores
      configuration_blocked behind the queue, naming the purpose and the record
      that owns no template
edge_cases: []
constraints: []
depends_on:
  - CAP-PFM-NOT-016
related:
  - CAP-PFM-NOT-018
  - CAP-PFM-NOT-019
  - CAP-PFM-NOT-020
journeys:
  - JNY-PFM-NOT-006
open_questions: []
evidence:
  - >-
    Pinned evidence inventory 1:
    services-platform:docs/communications/architecture.md;
    services-platform:modules/communications/tests/e2e/requests.e2e.spec.js;
    services-platform:modules/communications/tests/integration/services/communication-reads.integration.spec.js;
    services-platform:modules/communications/tests/integration/services/requests/handle-invocation.integration.spec.js
  - >-
    Pinned evidence inventory 2:
    services-platform:modules/communications/tests/integration/db/communication-request-submissions.table.integration.spec.js;
    services-platform:modules/communications/tests/integration/lifecycle/contact-role-compliance-communication.integration.spec.js;
    services-platform:modules/communications/tests/integration/services/dispatch/caller-rendered.integration.spec.js;
    services-platform:modules/communications/tests/integration/db/communication-messages.table.integration.spec.js
  - >-
    Pinned evidence inventory 3:
    services-platform:modules/communications/tests/integration/services/requests/submit-request.integration.spec.js;
    services-platform:modules/communications/tests/integration/services/storage/communication-objects.integration.spec.js;
    services-platform:modules/communications/tests/integration/services/dispatch/owned-template.integration.spec.js
controls: [TC-AVL-001, TC-AVL-004, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-DAT-001, TC-DAT-002, TC-DAT-003, TC-IAM-001, TC-IAM-002, TC-IAM-004, TC-IAM-005, TC-IAM-006, TC-LOG-004, TC-LOG-005, TC-MON-001, 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-TEN-001, TC-TEN-002, TC-TEN-003, TC-TEN-004, TC-VEN-001, TC-VEN-002, TC-VEN-003]
---

# Submit association communication requests

## Purpose

Applications submit association mail with durable request identity and inspectable acceptance outcomes.

## How It Works

A macroservice constructs a request using a known purpose and recipient contract. Submission validates the envelope before storing request data. Object storage precedes the pointer inserted in the business transaction. The committed pointer enables dispatch, while rollback may leave a temporary orphan object. Processing resolves templates or caller content and records acceptance or blocking reasons. Recipient safeguards produce queued or withheld message records under derived identities. Platform pins rendering inputs for later inspection. HTTP and IAM boundaries provide different authenticated entry points to the same request processing.

The rule rows define this outcome; the acceptance set identifies existing proof and proposed checks. Implementation evidence alone does not approve the observed details.

## Relationships

Published association content is required for Platform-rendered purposes; caller-rendered purposes follow their separate contract.
