---
id: CAP-CRM-ENT-007
title: Capture a person’s communication choices
product_area: crm
module: entities
revision: 1
status: approved
product_intent: confirmed
implementation_evidence: observed
source_sha:
  services-crm: 7ea48a414c42eade10d60753b8bcc323b1dd726f
  react-console: da63075876b2c5f4a6af07d457bb24ea8bf4c0c7
intent_sources:
  - 'services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#D-01'
  - >-
    services-crm:docs/milestones/CRM-M1-contacts-and-organizations.plan.md#2.
    Scope
purpose: Staff record a person’s communication restrictions and subscription choices.
outcomes:
  - >-
    Each contact carries its three restrictions and at most one preference per
    communication type.
  - The restrictions are visible on the Contact 360 header.
actors:
  - ACT-CRM-001
  - ACT-CRM-003
  - ACT-CRM-004
surfaces:
  - console
  - api
preconditions: []
postconditions: []
permissions:
  - key: crm.contacts.update
    where_checked: api
    scope: tenant
  - key: crm.contacts.read
    where_checked: api
    scope: tenant
rules:
  - id: BR-CAP-CRM-ENT-007-01
    proved_by: [ATS-CRM-ENT-007-04, ATS-CRM-ENT-007-06, ATS-CRM-ENT-007-07, TST-CRM-ENT-774]
    statement: >-
      Contacts default their do-not-mail, do-not-call and e-mail-opt-out flags
      to false.
    kind: invariant
    enforced_at:
      - database
    must_prove: false
    provenance: observed
    source: 'services-crm:modules/entities/db/contacts.table.ts:do_not_mail'
    origin: none
  - id: BR-CAP-CRM-ENT-007-02
    proved_by: [ATS-CRM-ENT-007-01, TST-CRM-ENT-787]
    statement: >-
      A contact stores one communication preference per communication type and
      rejects a repeated type at the second entry’s path.
    kind: validation
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
      a communication type listed twice, naming the second entry
    origin: none
  - id: BR-CAP-CRM-ENT-007-03
    proved_by: [ATS-CRM-ENT-007-02, TST-CRM-ENT-787]
    statement: >-
      Subscription addresses are canonical valid e-mail addresses, with blanks
      treated as absent.
    kind: validation
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/db/contact-communication-preferences.table.integration.spec.js#refuses
      a subscription value that is not shaped like an address by the domain’s
      email_address_format_check
    origin: none
  - id: BR-CAP-CRM-ENT-007-04
    proved_by: [ATS-CRM-ENT-007-03, TST-CRM-ENT-787]
    statement: >-
      Communication preferences include an enabled flag and allow subscribed,
      digest or unsubscribed as subscription status.
    kind: validation
    enforced_at:
      - api
      - database
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/db/contact-communication-preferences.table.integration.spec.js#refuses
      a subscription status outside the closed list by
      contact_communication_preferences_status_check
    origin: none
  - id: BR-CAP-CRM-ENT-007-05
    proved_by: [ATS-CRM-ENT-007-05]
    statement: The service accepts communication type identifiers it does not recognize.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: observed
    source: >-
      services-crm:modules/entities/services/persistence/contacts.ts:communicationPreferenceProblems
    origin: none
  - id: BR-CAP-CRM-ENT-007-06
    proved_by: [ATS-CRM-ENT-007-08]
    statement: >-
      The contacts-and-organizations milestone stores communication choices but
      leaves their application by message senders outside its scope.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: documented
    source: >-
      services-crm:docs/milestones/CRM-M1-contacts-and-organizations.plan.md#2.
      Scope
    origin: >-
      services-crm:docs/milestones/CRM-M1-contacts-and-organizations.plan.md#2.
      Scope
scenarios:
  - id: SC-CAP-CRM-ENT-007-01
    given: A member with Do Not Mail set.
    when: The staff member opens her Contact 360.
    then: Do Not Mail displays in the header.
    provenance: test
    source: >-
      react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0025.spec.ts#CRM-ACC-0025
edge_cases: []
constraints: []
depends_on: []
related:
  - CAP-CRM-ENT-001
  - CAP-CRM-ENT-003
  - CAP-CRM-ENT-012
journeys:
  - JNY-CRM-ENT-001
open_questions: []
feature: FEAT-CRM-ENT-03
concepts:
  - CON-CRM-ENT-001
evidence:
  - >-
    `services-crm` at `7ea48a414c42eade10d60753b8bcc323b1dd726f`: examined
    `services/persistence/contacts.ts` (preference problems), `db/contacts.table.ts`,
    `db/contact-communication-preferences.table.ts`; tests
    `integration/services/persistence/contacts.integration.spec.js`,
    `integration/db/contact-communication-preferences.table.integration.spec.js`.
  - >-
    `react-console` at `da63075876b2c5f4a6af07d457bb24ea8bf4c0c7`: examined inventory
    RUL-CRM-CON-021; journeys CRM-ACC-0025, 0026.
approved_by: Andrew
approved_at: 2026-10-22
controls: [TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-006, TC-DAT-001, TC-DAT-002, TC-DAT-003, TC-LOG-004, TC-LOG-005, TC-PI-001, TC-PI-003, TC-PRV-001, TC-PRV-002, TC-PRV-004, TC-TEN-001, TC-TEN-002, TC-TEN-003]
narrative:
  status: verified
  review: NARRATIVE-REVIEW-2026-10-23
narrative_sources:
  - section: Why It Works This Way
    provenance: inferred
    source: >-
      services-crm:modules/entities/db/contacts.table.ts:do_not_mail;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
      a communication type listed twice, naming the second entry;
      services-crm:modules/entities/tests/integration/db/contact-communication-preferences.table.integration.spec.js#refuses
      a subscription value that is not shaped like an address by the domain’s
      email_address_format_check;
      services-crm:modules/entities/tests/integration/db/contact-communication-preferences.table.integration.spec.js#refuses
      a subscription status outside the closed list by
      contact_communication_preferences_status_check;
      services-crm:modules/entities/services/persistence/contacts.ts:communicationPreferenceProblems;
      services-crm:docs/milestones/CRM-M1-contacts-and-organizations.plan.md#2. Scope
    note: >-
      Operational rationale inferred from the cited behavior; no personal motive or historical
      decision is asserted.
  - section: Example
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
      a communication type listed twice, naming the second entry;
      services-crm:modules/entities/tests/integration/db/contact-communication-preferences.table.integration.spec.js#refuses
      a subscription status outside the closed list by
      contact_communication_preferences_status_check
  - section: Relationships
    provenance: documented
    source: 'services-crm:docs/milestones/CRM-M1-contacts-and-organizations.plan.md#2. Scope'
repositories:
  - repo: services-crm
    role: owner
    paths:
      - modules/entities/
      - docs/milestones/CRM-M1-contacts-and-organizations.plan.md
      - modules/entities/db/contacts.table.ts
      - modules/entities/services/persistence/contacts.ts
      - >-
        modules/entities/tests/integration/db/contact-communication-preferences.table.integration.spec.js
      - >-
        modules/entities/tests/integration/services/persistence/contacts.integration.spec.js
  - repo: react-console
    role: console
    paths:
      - 'app/(console)/console/[tenantId]/crm/contacts/'
      - acceptance/crm/playwright/journeys/contacts/CRM-ACC-0025.spec.ts
exercised_by: [FAS-CRM-ENT-03-01, FAS-CRM-ENT-03-02, FAS-CRM-ENT-03-03, FAS-CRM-ENT-03-04, FAS-CRM-ENT-03-05]
suites: [TST-CRM-ENT-774, TST-CRM-ENT-787]
---

# Capture a person’s communication choices

## Purpose

A person's reachable address does not explain how that person wants communications handled. This capability stores contact-wide restrictions and per-communication-type choices, giving staff a place to record the member's instructions alongside the identity. It deliberately separates storing a preference from the responsibility of a sender to apply it.

## How It Works

Contacts carry broad do-not-mail, do-not-call, and e-mail-opt-out flags, initially false. They can also carry a preference entry for each communication type, with an enabled choice, subscription status, and optional subscription e-mail address. Supplied addresses use canonical valid e-mail values, and repeated communication types refuse the aggregate at the duplicate entry.

Today the service accepts a communication type identifier without establishing that it belongs to a recognized catalogue. The stored status distinguishes subscribed, digest, and unsubscribed. Staff retrieve the saved preferences with the contact, but this milestone does not establish how every message sender interprets or enforces those choices.

## Example

A membership coordinator records Ruth's request not to receive postal mail and stores a newsletter preference with digest status and her chosen subscription address. Reading the contact later shows both the general restriction and the specific subscription choice. This demonstrates persistent instructions; it does not demonstrate that a newsletter is delivered as a digest or that a postal campaign excludes Ruth.

Ruth uses ruth.kim@example.org for the newsletter and keeps a different address in her ordinary contact e-mail collection. The coordinator records that subscription destination explicitly. If she accidentally submits a second preference for the same newsletter with a competing enabled choice, the save identifies the repeated entry instead of choosing one of the two instructions. She corrects the submitted preferences and reads the contact again to check the saved result.

A later request to unsubscribe is a change to the stored instruction. Staff can inspect the unsubscribed status, but should verify any mailing outcome through the sender's own contract. In the same way, a digest status describes the saved choice here; it is not evidence of a digest schedule or a successful send. The capability test specification separates persistence and validation from those unestablished delivery outcomes.

## Why It Works This Way

Our reading is that broad channel restrictions and per-type preferences express different member instructions, so neither can safely substitute for the other. A single entry per communication type avoids competing settings for the same subscription. Canonical addresses prevent superficial spelling differences from changing the meaning of a stored destination.

The sender boundary matters because a reliable storage operation cannot prove a downstream campaign respects the choice. Acceptance of unrecognized communication types is current behavior, not proof of a product decision about catalogue authority. Keeping that uncertainty explicit prevents future engineers from assuming a validated subscription catalogue already exists.

The enabled choice and subscription status remain distinct stored properties. A reader must not infer a universal precedence rule between them or between a subscription and the broad channel flags: the capability establishes their storage, while the sender's interpretation is outside the milestone. This is a boundary for future changes as well as for staff expectations.

## Relationships

Establish a person’s CRM record and Save corrections without overwriting another change persist these preferences with the contact. Maintain reliable contact points owns the person's ordinary e-mail collection, which differs from a subscription-specific address. Platform communications owns message sending; compliance notices have their own recipient selection contract and must not inherit unspecified preference behavior from this capability.
