KB-30DB

12 — Customer-care / Email / Message — Forward Scope (design seam only)

9 min read Revision 1
design-packdieu-45customer-careemailmessageforward-scopedesign-seamdesign-only

12 — Customer-care / Email / Message — Forward Scope (design seam only)

DESIGN-ONLY SEAM. Cites Điều 45 §13.1, §13.2, §13.3. No concrete customer/email/message schema. No PII handling concrete. No body storage location chosen.


§1. Goal

Carve forward-compatible seams in the queue substrate so that, when customer-care / email / messaging domains land (per §13.2 forward-compatibility clause), they can be plugged in without redesign — and without ever violating §4 (signal-not-data) or §14 (NVSZ).

Specifically:

  1. Define the 7 job kinds that a customer interaction will likely need.
  2. Specify where the message body, conversation log, and attachments live (never in queue).
  3. Specify how PII boundary holds in safe_payload CHECK.
  4. Specify approval-before-send invariant.
  5. Specify escalation seam.

This document does not define customer_* / email_* tables. Those belong in their own sub-design pack when product scope is decided.


§2. Current state

Concern Live
Customer table ❌ none in PG
Conversation log ❌ none in PG
Email ingest ❌ no IMAP/SMTP pipeline
Inbound message webhook ❌ none
event_domain vocab room available: event_type_registry allows new (domain, type, stream, lane) rows via §6.4 ratification

§3. Proposed seven seam-shaped job kinds

forward_job_kinds:
  - customer_message_inbound        # one inbound message captured; payload_ref → conversation store
  - customer_message_classify       # categorise (intent, priority, language)
  - customer_message_draft          # AI/agent writes a draft reply
  - customer_message_approve        # human approval step (review queue)
  - customer_message_send           # post the reply via channel
  - customer_message_followup       # scheduled follow-up touch
  - customer_message_escalate       # routes to higher-tier agency

Each is a job_queue.job_kind value. No new substrate beyond what DP2 already provides.


§4. Payload boundary (signal-not-data, mandatory)

NEVER in safe_payload:                            ALWAYS via payload_ref:
  message body (any length)                        conversation_id (uuid)
  message subject (treat as restricted)            message_id (uuid)
  customer name                                    case_id
  email address                                    customer_ref (canonical_address)
  phone number                                     attachment uri (blob storage)
  attachment binary
  PII of any kind

safe_payload may carry:                            
  language (BCP-47)                                
  intent_code (from classify)                      
  priority (smallint)                              
  channel_kind ('email','sms','webchat',...)
  routing_hint (agency:..., role:...)
  confidence (numeric)
  retry_reason_code

Existing safe_payload CHECK on event_outbox already forbids body|content|raw|vector|embedding|secret|token|password|ssn|personal_data. DP2 mirrors this CHECK to job_queue. Customer payload classes that could contain message text MUST go via payload_ref only.


§5. Storage seam (future)

When the customer-care domain lands, the message store will likely be:

  • customer_conversation table (one per conversation thread).
  • customer_message table (one per inbound/outbound message; body column subject to encryption-at-rest).
  • Attachments in blob storage; attachment_ref URI in PG.

These tables do NOT live in the queue substrate. The queue substrate carries refs to them via payload_ref. Cross-references are FK (customer_message_id foreign key referenced by job_queue.payload_ref typed as text, validated by trigger only at execution-time inside the executor).


§6. Approval seam (customer_message_send invariant)

Per §13.2 rule 1 + forward-compat principle, customer_message_send MUST NOT execute without an approved predecessor unless explicitly dispensed:

NON-EXECUTABLE DESIGN SKETCH — DO NOT APPLY

claim filter for customer_message_send:
  - require parent_job_id refers to a customer_message_approve in 'succeeded' status
  - OR require dot_config.queue.customer.send_without_approve.enabled = true
      AND a workflow_admin signed-off the customer_id (auditable trail)

Default: approve-required. Dispensation requires explicit Council-approved flag + per-customer signoff.


§7. Escalation seam

customer_message_escalate flips the target_ref to a higher-tier recipient (agency role) and re-enqueues the conversation handling:

escalation_pattern:
  trigger:
    - SLA breach (deadline_at passed)
    - explicit operator decision
    - classifier confidence below threshold
  effect:
    - INSERT job_queue (job_kind='customer_message_escalate',
                       target_ref=<higher-tier role>,
                       correlation_id=<conversation_id>,
                       parent_job_id=<originating job>)
    - emit event customer.message.escalated (event_type to register)

§8. Lifecycle / status

All seven kinds use the §6.7 9-state machine via DP2 substrate. No custom lifecycle.


§9. Indexes / performance

Same partial indexes as DP2 (status='queued', lease_active, dead_letter). One additional candidate index when adoption begins:

NON-EXECUTABLE DESIGN SKETCH — DO NOT APPLY

PARTIAL INDEX ix_job_queue_customer_followup
  ON job_queue(process_after, job_kind)
  WHERE job_kind='customer_message_followup' AND status IN ('queued','retry_waiting');

For SLA monitoring of customer interactions.


§10. Security / governance

  • PII boundary is enforced at safe_payload CHECK; producers refused at INSERT if PII leaks.
  • Customer message store would have its own row-level security per agency.
  • customer_message_send is the only externally-visible job; gated by approve invariant + executor whitelist.
  • Audit trail via dot_iu_command_run + event_outbox events.

§11. Rollback / disable

  • dot_config.queue.customer.enabled = false → enqueue refused.
  • Per-kind: dot_config.queue.customer.<kind>.enabled overrides.
  • Send dispensation: independent flag (default off).

§12. Healthcheck / observability

  • v_queue_health (DP4) shows backlog per customer_message_* job_kind.
  • Optional v_customer_sla_pressure view aggregates deadline_at proximity.
  • DLQ rows by customer_message_send indicate channel outage.

§13. Compatibility with Điều 45 v1.0

Clause Compliance
§13.1 no customer substrate at law enactment ✅ none added here
§13.2 forward-compat clause ✅ vocab seam reserved; payload_ref pattern preserved
§13.3 not Phase 1 of Điều 45 ✅ this design pack does not implement; it carves the seam
§4 signal-not-data ✅ explicit PII boundary table in §4
§6.4 inclusion criteria ✅ each customer.* event_type will require ratification before live emission
§11.5 executor whitelist ✅ all customer jobs assigned to whitelisted executors
§13.4 MOT clause ✅ customer flows may be MOT-orchestrated; MOT remains non-executor

§14. Implementation prerequisites (future Phase 7)

  • DP2 (job_queue) in production.
  • Customer message store (separate design pack).
  • Channel adapters (Hermes / external_worker for SMTP/IMAP/webchat).
  • Council ratification of customer.* event_type vocab additions.
  • Privacy / retention policy ratification (separate pack).

§15. Open questions

# Question Routed to
CM-Q1 Approve the 7 seam-job_kinds vocabulary at Phase 1 (vocab reservation only)? Council
CM-Q2 Approve approve-required default + dispensation flag? Council + Legal
CM-Q3 Should customer_message_inbound create an event_outbox event before its job? Council
CM-Q4 SLA deadline modelling: deadline_at on job_queue sufficient, or separate customer_sla table? Council
CM-Q5 Encryption at rest for body — pgcrypto vs pg_tle vs application-layer? Council
CM-Q6 Multi-channel routing (email vs SMS vs webchat) — single executor per channel, or routing layer? Council

Customer/email/message forward scope. Seam only. No mutation. Authored 2026-05-26.

Back to Knowledge Hub knowledge/dev/laws/dieu44-trien-khai/v0.6-dieu45-full-queue-orchestration-design-pack/12-customer-email-message-forward-scope.md