generated from coulomb/repo-seed
1700 lines
64 KiB
Markdown
1700 lines
64 KiB
Markdown
I used current provider documentation and industry guidance to ground the SMS model. Twilio’s outbound status model includes states such as queued/sending/sent/delivered/undelivered/failed, and notes that `sent` may remain without final delivery status in some cases. ([help.twilio.com][1]) Vonage’s SMS DLRs include `accepted`, `delivered`, `buffered`, `expired`, `failed`, `rejected`, and `unknown`; Vonage also notes DLRs are returned when received from downstream carriers. ([developer.vonage.com][2]) Sinch’s SMS delivery reports use statuses such as queued, dispatched, delivered and error codes, while Bird/MessageBird distinguish delivered from not delivered and provide reason codes such as unknown subscriber. ([developers.sinch.com][3]) SMS content length, encoding, and concatenation matter because GSM-7, UCS-2, and multi-segment messages affect cost, delivery behavior, and diagnostics. ([Twilio][4]) For compliance-sensitive SMS, opt-in/opt-out, STOP/HELP handling, and sender registration are first-class channel concerns; Twilio documents Advanced Opt-Out and A2P 10DLC registration, while CTIA guidance emphasizes documenting consent and respecting opt-out. ([Twilio][5])
|
||
|
||
# SmsAdapterSpecification.md
|
||
|
||
## 1. Document Status
|
||
|
||
**Document:** SmsAdapterSpecification.md
|
||
**Project:** sms-connect
|
||
**Target Integration:** coordination-engine
|
||
**Adapter Contract:** AdapterInterfaceSpecification.md v1.0
|
||
**Status:** Draft v1.0
|
||
**Primary Scope:** SMS protocol and provider integration as a coordination-engine adapter
|
||
|
||
## 2. Purpose
|
||
|
||
This document specifies how `sms-connect` models SMS as a communication protocol and how it integrates with `coordination-engine`.
|
||
|
||
`sms-connect` is a channel adapter for short-message communication. It provides SMS-specific sending, delivery receipt ingestion, status normalization, phone endpoint quality handling, opt-out/suppression handling, inbound reply processing, evidence classification, and provider abstraction.
|
||
|
||
SMS is often stronger than email for endpoint-level delivery evidence because carriers and providers may return delivery receipts indicating that a message reached the receiving network or handset. However, SMS still does not normally prove human awareness, comprehension, identity-bound interaction, payload access, or final coordination success.
|
||
|
||
The key design objective is to make SMS useful as a notification, reminder, escalation, and lightweight interaction channel without overclaiming what SMS can prove.
|
||
|
||
## 3. Core Principle
|
||
|
||
SMS is a medium-strength notification channel with better endpoint-delivery evidence than email in many cases, but it is still not a reliable proof channel for human awareness or result completion.
|
||
|
||
The adapter MUST distinguish:
|
||
|
||
```text
|
||
provider acceptance
|
||
carrier or downstream network acceptance
|
||
delivery receipt
|
||
temporary delivery uncertainty
|
||
permanent delivery failure
|
||
recipient reply
|
||
opt-out or suppression
|
||
link interaction
|
||
identity-bound interaction from another system
|
||
coordination result evidence
|
||
```
|
||
|
||
In the coordination model, SMS delivery receipts can support stronger notification evidence than email MX acceptance, but SMS events alone usually remain insufficient for high-assurance scenarios unless the intended result explicitly accepts SMS delivery or SMS reply as sufficient.
|
||
|
||
## 4. Architectural Role
|
||
|
||
### 4.1 Standalone Role
|
||
|
||
As a standalone component, `sms-connect` provides:
|
||
|
||
* provider-neutral SMS sending
|
||
* transactional SMS dispatch
|
||
* provider and carrier delivery receipt ingestion
|
||
* message status normalization
|
||
* phone number endpoint diagnostics
|
||
* SMS segmentation and encoding diagnostics
|
||
* inbound SMS reply handling
|
||
* opt-in, opt-out, HELP, START, and STOP keyword handling
|
||
* suppression management
|
||
* sender identity and campaign metadata tracking
|
||
* SMS message timelines
|
||
* delivery failure classification
|
||
* channel health and compliance diagnostics
|
||
* normalized SMS evidence stream
|
||
|
||
### 4.2 coordination-engine Adapter Role
|
||
|
||
As a `coordination-engine` adapter, `sms-connect` provides:
|
||
|
||
#### Actions
|
||
|
||
* `notification.send`
|
||
* `notification.send_reminder`
|
||
* `notification.schedule`
|
||
* `notification.cancel` where technically possible before dispatch
|
||
* `recipient.suppress`
|
||
* `recipient.unsuppress`
|
||
* `recipient.opt_in_record`
|
||
* `recipient.opt_out_record`
|
||
* `notification.register_tracking_context`
|
||
|
||
#### Signals
|
||
|
||
* `notification.attempt.accepted_by_adapter`
|
||
* `notification.attempt.rejected_by_adapter`
|
||
* `notification.attempt.accepted_by_provider`
|
||
* `notification.attempt.rejected_by_provider`
|
||
* `notification.attempt.queued`
|
||
* `notification.attempt.scheduled`
|
||
* `notification.attempt.delayed`
|
||
* `notification.endpoint.accepted`
|
||
* `notification.endpoint.deferred`
|
||
* `notification.endpoint.rejected_temporary`
|
||
* `notification.endpoint.rejected_permanent`
|
||
* `notification.endpoint.unreachable`
|
||
* `notification.endpoint.unknown`
|
||
* `notification.channel.suppression_added`
|
||
* `notification.channel.suppression_removed`
|
||
* `notification.channel.complaint_received`
|
||
* `notification.channel.unsubscribe_received`
|
||
* `notification.channel.reputation_warning`
|
||
* `interaction.reply_received`
|
||
* `interaction.acknowledgement_recorded`
|
||
* `interaction.unverified_actor_interaction`
|
||
* `interaction.authenticated_actor_interaction` only when paired with trusted identity evidence
|
||
* `system.provider.degraded`
|
||
* `system.provider.unavailable`
|
||
|
||
## 5. Relationship to coordination-engine
|
||
|
||
`sms-connect` does not own:
|
||
|
||
* `CoordinationCase`
|
||
* intended result evaluation
|
||
* participant-level success
|
||
* case-level success
|
||
* legal notification interpretation
|
||
* portal access
|
||
* payload retrieval
|
||
* payment settlement
|
||
* signature completion
|
||
* multi-channel escalation policy
|
||
* final closure decisions
|
||
|
||
`sms-connect` owns:
|
||
|
||
* SMS send requests
|
||
* SMS provider abstraction
|
||
* provider and carrier identifiers
|
||
* SMS delivery receipt ingestion
|
||
* SMS status classification
|
||
* SMS-native message timeline
|
||
* phone endpoint diagnostics
|
||
* SMS reply classification
|
||
* opt-out and suppression handling
|
||
* SMS evidence mapping
|
||
* SMS-specific uncertainty classification
|
||
|
||
The boundary rule is:
|
||
|
||
> sms-connect reports what happened in the SMS channel and what that may indicate. coordination-engine decides what that means for the coordination case.
|
||
|
||
## 6. SMS as Notification, Lightweight Payload, and Response Channel
|
||
|
||
Within `coordination-engine`, SMS is primarily a **notification and reminder channel**.
|
||
|
||
SMS may also carry lightweight inline payloads or action prompts, such as:
|
||
|
||
* one-time codes
|
||
* short status alerts
|
||
* short links
|
||
* payment links
|
||
* confirmation prompts
|
||
* reply keywords
|
||
* appointment reminders
|
||
* urgent escalation notices
|
||
|
||
However, for most coordination scenarios, SMS should point participants toward an action surface such as:
|
||
|
||
* portal page
|
||
* mobile app screen
|
||
* payment page
|
||
* signature flow
|
||
* upload form
|
||
* approval screen
|
||
* support flow
|
||
|
||
The actual payload access or result-relevant action is usually observed by another adapter.
|
||
|
||
Example:
|
||
|
||
```text
|
||
sms-connect:
|
||
notification.endpoint.accepted
|
||
interaction.reply_received
|
||
|
||
portal-connect:
|
||
identity.actor_authenticated
|
||
delivery.payload.downloaded
|
||
|
||
coordination-engine:
|
||
participant result satisfied
|
||
```
|
||
|
||
SMS delivery to handset or network is stronger than provider acceptance but still does not prove that the intended human read, understood, or acted on the message.
|
||
|
||
## 7. SMS Message Lifecycle Model
|
||
|
||
`sms-connect` models an SMS notification as a lifecycle with observable phases.
|
||
|
||
```text
|
||
message.created
|
||
message.rendered
|
||
message.render_failed
|
||
message.segmentation_analyzed
|
||
message.consent_checked
|
||
message.suppression_checked
|
||
message.send_requested
|
||
message.accepted_by_adapter
|
||
message.rejected_by_adapter
|
||
message.accepted_by_provider
|
||
message.rejected_by_provider
|
||
message.queued
|
||
message.scheduled
|
||
message.dispatched
|
||
message.accepted_by_carrier
|
||
message.buffered
|
||
message.deferred
|
||
message.delivered
|
||
message.undelivered
|
||
message.failed
|
||
message.rejected
|
||
message.expired
|
||
message.unknown
|
||
message.inbound_reply_received
|
||
message.opt_out_received
|
||
message.opt_in_received
|
||
message.help_received
|
||
message.link_clicked
|
||
message.suppression_added
|
||
message.channel_warning
|
||
```
|
||
|
||
The lifecycle is not strictly linear. Events may arrive late, out of order, duplicated, partially aggregated, or missing.
|
||
|
||
## 8. SMS Message vs Attempt vs Segment vs Recipient
|
||
|
||
The adapter MUST distinguish at least four layers.
|
||
|
||
### 8.1 SmsMessage
|
||
|
||
The logical SMS message created by a client or coordination case.
|
||
|
||
```yaml
|
||
SmsMessage:
|
||
sms_message_id: string
|
||
coordination_case_id: string?
|
||
participant_id: string?
|
||
purpose: string?
|
||
message_body_ref: string?
|
||
template_ref: string?
|
||
tracking_context: TrackingContext
|
||
created_at: timestamp
|
||
```
|
||
|
||
### 8.2 SmsAttempt
|
||
|
||
One send attempt through one provider/configuration.
|
||
|
||
```yaml
|
||
SmsAttempt:
|
||
sms_attempt_id: string
|
||
sms_message_id: string
|
||
provider_name: string
|
||
provider_account_ref: string?
|
||
sender_endpoint: SmsEndpoint
|
||
recipient_endpoint: SmsEndpoint
|
||
provider_message_id: string?
|
||
adapter_operation_id: string?
|
||
sender_identity_ref: string?
|
||
campaign_ref: string?
|
||
state: SmsAttemptState
|
||
created_at: timestamp
|
||
updated_at: timestamp
|
||
```
|
||
|
||
### 8.3 SmsSegment
|
||
|
||
A logical SMS may be split into multiple billable/deliverable segments.
|
||
|
||
```yaml
|
||
SmsSegment:
|
||
sms_segment_id: string
|
||
sms_attempt_id: string
|
||
segment_index: integer
|
||
segment_count: integer
|
||
encoding: gsm7 | ucs2 | binary | unknown
|
||
provider_segment_id: string?
|
||
carrier_segment_id: string?
|
||
state: SmsSegmentState
|
||
delivered_at: timestamp?
|
||
failed_at: timestamp?
|
||
```
|
||
|
||
Segmentation matters for cost, deliverability diagnostics, and partial-failure interpretation. For coordination evidence, a multi-segment message should normally be considered delivered only when the provider or carrier reports the message as delivered or enough segment-level evidence exists according to provider semantics.
|
||
|
||
### 8.4 SmsEndpoint
|
||
|
||
The target or sender endpoint.
|
||
|
||
```yaml
|
||
SmsEndpoint:
|
||
endpoint_id: string?
|
||
endpoint_type: phone_number | short_code | long_code | toll_free | alphanumeric_sender_id | sender_pool | unknown
|
||
phone_number_e164: string?
|
||
sender_id: string?
|
||
display_name: string?
|
||
country_code: string?
|
||
verification_state: unknown | syntax_validated | verified | suspected_invalid | invalid
|
||
consent_state: unknown | opted_in | opted_out | consent_not_required | consent_required | disputed
|
||
suppression_state: active | suppressed | opt_out_suppressed | bounce_suppressed | provider_suppressed | manual_suppressed | unknown
|
||
metadata: object?
|
||
```
|
||
|
||
Per-recipient modeling is required. The preferred coordination mode is one logical notification attempt per participant endpoint.
|
||
|
||
## 9. SMS Attempt States
|
||
|
||
The adapter SHOULD support these attempt states:
|
||
|
||
```text
|
||
created
|
||
rendered
|
||
render_failed
|
||
consent_blocked
|
||
suppressed
|
||
send_requested
|
||
accepted_by_adapter
|
||
rejected_by_adapter
|
||
accepted_by_provider
|
||
rejected_by_provider
|
||
queued
|
||
scheduled
|
||
dispatched
|
||
accepted_by_carrier
|
||
buffered
|
||
deferred
|
||
delivered
|
||
undelivered
|
||
failed
|
||
rejected
|
||
expired
|
||
unknown
|
||
reply_received
|
||
opt_out_received
|
||
opt_in_received
|
||
help_received
|
||
channel_degraded
|
||
```
|
||
|
||
These states are SMS-native. They are not coordination result states.
|
||
|
||
## 10. SMS Evidence Assessment
|
||
|
||
`sms-connect` should provide an SMS-native assessment separate from coordination-engine state.
|
||
|
||
```yaml
|
||
SmsEvidenceAssessment:
|
||
sms_message_id: string
|
||
participant_id: string?
|
||
category: success | fail | undef
|
||
subclass: string
|
||
confidence: low | medium | high
|
||
strongest_signal: string?
|
||
evidence_summary:
|
||
- string
|
||
recommended_coordination_interpretation: string?
|
||
```
|
||
|
||
The assessment categories are adapter-level hints.
|
||
|
||
### 10.1 SMS Adapter Success
|
||
|
||
SMS-level `success` means the SMS channel produced strong SMS-channel evidence.
|
||
|
||
Possible subclasses:
|
||
|
||
```text
|
||
success.provider_accepted
|
||
success.carrier_accepted
|
||
success.delivered
|
||
success.reply_received
|
||
success.acknowledgement_reply_received
|
||
success.link_clicked
|
||
```
|
||
|
||
Important: These are not automatically coordination success.
|
||
|
||
`success.delivered` usually means a provider or carrier reports delivery to the handset or receiving network. It is stronger than email server acceptance, but it still does not prove human awareness unless scenario policy accepts delivery receipt as sufficient.
|
||
|
||
### 10.2 SMS Adapter Fail
|
||
|
||
SMS-level `fail` indicates strong evidence that the SMS channel failed or should not be used.
|
||
|
||
Subclasses:
|
||
|
||
```text
|
||
fail.missing_number
|
||
fail.invalid_number_format
|
||
fail.not_mobile_number
|
||
fail.unknown_subscriber
|
||
fail.unreachable_subscriber
|
||
fail.unavailable_subscriber
|
||
fail.ported_number_issue
|
||
fail.blocked_by_recipient
|
||
fail.opted_out
|
||
fail.consent_missing
|
||
fail.suppressed
|
||
fail.provider_rejected
|
||
fail.carrier_rejected
|
||
fail.filtered_by_carrier
|
||
fail.policy_rejected
|
||
fail.content_rejected
|
||
fail.sender_not_registered
|
||
fail.sender_not_allowed
|
||
fail.route_unavailable
|
||
fail.expired
|
||
fail.delivery_failed
|
||
fail.message_too_long_or_invalid
|
||
fail.encoding_unsupported
|
||
fail.account_or_quota_failure
|
||
```
|
||
|
||
### 10.3 SMS Adapter Undef
|
||
|
||
SMS-level `undef` is used when the adapter cannot determine final delivery or human awareness.
|
||
|
||
Subclasses:
|
||
|
||
```text
|
||
undef.pending
|
||
undef.provider_accepted_only
|
||
undef.queued
|
||
undef.dispatched
|
||
undef.carrier_accepted_only
|
||
undef.buffered
|
||
undef.deferred
|
||
undef.no_dlr
|
||
undef.dlr_unknown
|
||
undef.delivery_status_unknown
|
||
undef.delivery_receipt_missing
|
||
undef.delivered_but_awareness_unproven
|
||
undef.link_clicked_identity_uncertain
|
||
undef.reply_identity_uncertain
|
||
undef.conflicting_evidence
|
||
undef.channel_degraded
|
||
undef.expired_unknown
|
||
```
|
||
|
||
The `undef` category must not be empty because SMS delivery reporting is provider-, route-, country-, carrier-, and device-dependent.
|
||
|
||
## 11. Detailed SMS Scenario Classification
|
||
|
||
### 11.1 Pre-Send Scenarios
|
||
|
||
| Scenario | SMS assessment | Normalized event | Notes |
|
||
| -------------------------------------- | --------------------------------------------------------------- | ------------------------------------------ | ---------------------------------------- |
|
||
| Missing number | `fail.missing_number` | `notification.attempt.rejected_by_adapter` | No send possible |
|
||
| Invalid phone number syntax | `fail.invalid_number_format` | `notification.attempt.rejected_by_adapter` | Strong local failure |
|
||
| Number not suitable for SMS | `fail.not_mobile_number` | `notification.attempt.rejected_by_adapter` | Landline or unsupported endpoint |
|
||
| Missing consent where required | `fail.consent_missing` | `notification.attempt.rejected_by_adapter` | Policy/compliance block |
|
||
| Recipient opted out | `fail.opted_out` | `notification.channel.suppression_added` | Channel blocked |
|
||
| Suppression hit | `fail.suppressed` | `notification.channel.suppression_added` | Channel blocked |
|
||
| Template rendering failure | `fail.message_too_long_or_invalid` or `fail.provider_rejected` | `notification.attempt.rejected_by_adapter` | Notification unusable |
|
||
| Message contains invalid link | `fail.message_too_long_or_invalid` or scenario-specific failure | `notification.attempt.rejected_by_adapter` | Especially relevant for portal links |
|
||
| Sender/campaign missing | `fail.sender_not_registered` or `fail.sender_not_allowed` | `notification.attempt.rejected_by_adapter` | Common in regulated routes |
|
||
| Provider unavailable before acceptance | `undef.pending` or `fail.provider_rejected` | action error | Depends on retryability |
|
||
| Submission timeout | `undef.pending` | action result unknown | Could have been accepted despite timeout |
|
||
|
||
### 11.2 Provider-Side Scenarios
|
||
|
||
| Scenario | SMS assessment | Normalized event | Notes |
|
||
| ------------------------------ | ------------------------------- | ------------------------------------------------------- | ------------------------------ |
|
||
| Adapter accepted request | `undef.pending` | `notification.attempt.accepted_by_adapter` | Adapter accepted work |
|
||
| Provider accepted message | `undef.provider_accepted_only` | `notification.attempt.accepted_by_provider` | Sending began |
|
||
| Provider rejected message | `fail.provider_rejected` | `notification.attempt.rejected_by_provider` | Strong attempt failure |
|
||
| Provider queued message | `undef.queued` | `notification.attempt.queued` | Pending |
|
||
| Provider scheduled message | `undef.pending` | `notification.attempt.scheduled` | Not yet sent |
|
||
| Provider dispatched message | `undef.dispatched` | `notification.attempt.accepted_by_provider` or metadata | Provider handed off downstream |
|
||
| Provider delayed message | `undef.deferred` | `notification.attempt.delayed` | Pending/degraded |
|
||
| Provider route unavailable | `fail.route_unavailable` | `notification.attempt.rejected_by_provider` | Strong channel/route failure |
|
||
| Provider quota/account failure | `fail.account_or_quota_failure` | `notification.attempt.rejected_by_provider` | Operational failure |
|
||
| Provider suppression | `fail.suppressed` | `notification.channel.suppression_added` | Channel blocked |
|
||
|
||
### 11.3 Carrier and Downstream Network Scenarios
|
||
|
||
| Scenario | SMS assessment | Normalized event | Notes |
|
||
| ---------------------------- | ----------------------------------------- | --------------------------------------------------------------- | ---------------------------------------------------- |
|
||
| Carrier accepted | `undef.carrier_accepted_only` | `notification.endpoint.accepted` | Stronger than provider-only, still not handset proof |
|
||
| Buffered | `undef.buffered` | `notification.endpoint.deferred` | Held for later delivery |
|
||
| Deferred | `undef.deferred` | `notification.endpoint.deferred` | Retry or wait |
|
||
| Delivered | `success.delivered` | `notification.endpoint.accepted` with delivery metadata | Strong endpoint evidence, not human awareness |
|
||
| Undelivered | `fail.delivery_failed` | `notification.endpoint.rejected_permanent` or temporary | Use reason code |
|
||
| Failed | `fail.delivery_failed` | `notification.endpoint.rejected_permanent` | Strong negative evidence if final |
|
||
| Rejected | `fail.carrier_rejected` | `notification.endpoint.rejected_permanent` | Carrier refused delivery |
|
||
| Expired | `fail.expired` or `undef.expired_unknown` | `notification.endpoint.unknown` or rejected temporary/permanent | Depends on provider semantics |
|
||
| Unknown | `undef.dlr_unknown` | `notification.endpoint.unknown` | No useful final status |
|
||
| No DLR received | `undef.no_dlr` | none or timeout event | Common on some routes |
|
||
| Delivery receipt late | update current assessment | corresponding event | Must be accepted if relevant |
|
||
| Conflicting delivery reports | `undef.conflicting_evidence` | multiple events | Preserve all evidence |
|
||
|
||
### 11.4 Device and Recipient Availability Scenarios
|
||
|
||
| Scenario | SMS assessment | Notes |
|
||
| -------------------------------- | ------------------------------------------------- | ---------------------------------------------- |
|
||
| Handset off or unreachable | `undef.deferred` or `fail.unreachable_subscriber` | Depending on final DLR |
|
||
| Subscriber unavailable | `undef.deferred` or `fail.unavailable_subscriber` | Carrier-specific |
|
||
| Unknown subscriber | `fail.unknown_subscriber` | Remove or verify number |
|
||
| Roaming issue | `undef.deferred` or `fail.route_unavailable` | Route/country dependent |
|
||
| Blocked by recipient | `fail.blocked_by_recipient` | If provider/carrier reports it |
|
||
| Carrier filtering | `fail.filtered_by_carrier` | Often content/campaign/sender issue |
|
||
| Device received but user ignored | `undef.delivered_but_awareness_unproven` | Delivery does not prove reading |
|
||
| SIM swap/number reassigned | `undef.identity_uncertain` if suspected | Requires external identity/number intelligence |
|
||
| Shared phone | `undef.identity_uncertain` | Intended participant uncertain |
|
||
| Corporate device management | `undef.delivered_but_awareness_unproven` | User awareness uncertain |
|
||
|
||
### 11.5 Link Interaction Scenarios
|
||
|
||
SMS itself does not provide open tracking. Link interaction must be observed through tracked links or action surfaces.
|
||
|
||
| Scenario | SMS assessment | Normalized event | Notes |
|
||
| -------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------ | -------------------------------------- |
|
||
| No link click | `undef.no_signal` | none | Not evidence of failure |
|
||
| Link clicked | `success.link_clicked` or `undef.link_clicked_identity_uncertain` | `interaction.unverified_actor_interaction` | Identity uncertain |
|
||
| Link clicked then authenticated portal login | SMS event remains weak; portal event is strong | Portal adapter provides identity evidence | |
|
||
| Link clicked then payload downloaded | SMS contributed path evidence; delivery evidence is decisive | Portal/delivery adapter closes result | |
|
||
| Link scanner or preview fetch | `undef.link_clicked_identity_uncertain` or scanner-like if detected | `interaction.scanner_or_bot_interaction` | Less common than email but possible |
|
||
| Expired link clicked | `undef.link_clicked_identity_uncertain` | interaction plus access failure | Awareness possible, action path failed |
|
||
| Forwarded SMS link clicked | `undef.identity_uncertain` | unverified interaction | Intended actor unknown |
|
||
|
||
### 11.6 Inbound Reply Scenarios
|
||
|
||
SMS can be a useful lightweight response channel.
|
||
|
||
| Scenario | SMS assessment | Normalized event | Notes |
|
||
| ----------------------------------------------- | ---------------------------------------- | ------------------------------------------------------------- | ----------------------------------------------------------- |
|
||
| Any reply received | `success.reply_received` | `interaction.reply_received` | Strong awareness signal, identity may still need validation |
|
||
| YES/OK/ACK reply | `success.acknowledgement_reply_received` | `interaction.acknowledgement_recorded` if policy accepts | Can be result evidence in low/medium assurance cases |
|
||
| STOP / unsubscribe | `fail.opted_out` | `notification.channel.unsubscribe_received` + suppression | Future channel constraint |
|
||
| START / opt-in | active/unknown | `notification.channel.suppression_removed` or opt-in metadata | Channel may become usable |
|
||
| HELP | neutral | `interaction.reply_received` | Support/compliance event |
|
||
| Free-text question | `success.reply_received` | `interaction.reply_received` | May trigger support workflow |
|
||
| Reply from reassigned/shared number | `undef.reply_identity_uncertain` | `interaction.reply_received` | Identity risk |
|
||
| Reply from unsupported alphanumeric sender path | no inbound possible | capability limitation | Must be declared by adapter |
|
||
|
||
A reply is often stronger awareness evidence than a delivery receipt, but it still may not prove that the intended participant acted unless phone-number ownership and context are sufficient for the scenario.
|
||
|
||
### 11.7 Opt-Out, Consent, and Suppression Scenarios
|
||
|
||
| Scenario | SMS assessment | Normalized event | Notes |
|
||
| -------------------- | ----------------------- | --------------------------------------------------------- | --------------------------- |
|
||
| Consent recorded | channel usable | adapter metadata or evidence | May be required before send |
|
||
| Consent missing | `fail.consent_missing` | `notification.attempt.rejected_by_adapter` | Local policy block |
|
||
| STOP received | `fail.opted_out` | `notification.channel.unsubscribe_received` + suppression | Channel blocked |
|
||
| START received | channel may be restored | `notification.channel.suppression_removed` | Provider/country dependent |
|
||
| HELP received | neutral/support event | `interaction.reply_received` | May trigger help response |
|
||
| Manual suppression | `fail.suppressed` | `notification.channel.suppression_added` | Operator action |
|
||
| Provider suppression | `fail.suppressed` | `notification.channel.suppression_added` | Provider-managed |
|
||
| Suppression removed | active/unknown | `notification.channel.suppression_removed` | Channel may become usable |
|
||
|
||
For non-marketing or legally required notifications, opt-out semantics must be scenario-specific. `sms-connect` records the event; `coordination-engine` decides whether SMS remains permissible, whether fallback is needed, or whether manual handling is required.
|
||
|
||
## 12. Adapter-to-Coordination Mapping
|
||
|
||
### 12.1 Core Mapping Table
|
||
|
||
| SMS-native event | SMS assessment | coordination-engine event | Coordination interpretation |
|
||
| -------------------- | ----------------------------------------- | -------------------------------------------------------- | ----------------------------------------- |
|
||
| message created | `undef.pending` | `notification.attempt.created` | Attempt exists |
|
||
| render failed | `fail.message_too_long_or_invalid` | `notification.attempt.rejected_by_adapter` | Notification failed before send |
|
||
| consent block | `fail.consent_missing` | `notification.attempt.rejected_by_adapter` | Local policy block |
|
||
| suppression hit | `fail.suppressed` | `notification.channel.suppression_added` | Channel blocked |
|
||
| adapter accepted | `undef.pending` | `notification.attempt.accepted_by_adapter` | Work accepted |
|
||
| adapter rejected | adapter-specific fail | `notification.attempt.rejected_by_adapter` | Attempt failed |
|
||
| provider accepted | `undef.provider_accepted_only` | `notification.attempt.accepted_by_provider` | Weak send evidence |
|
||
| provider rejected | `fail.provider_rejected` | `notification.attempt.rejected_by_provider` | Strong attempt failure |
|
||
| provider queued | `undef.queued` | `notification.attempt.queued` | Pending |
|
||
| dispatched | `undef.dispatched` | `notification.attempt.accepted_by_provider` + metadata | Sent downstream |
|
||
| carrier accepted | `undef.carrier_accepted_only` | `notification.endpoint.accepted` | Medium endpoint evidence |
|
||
| buffered | `undef.buffered` | `notification.endpoint.deferred` | Pending |
|
||
| delivered | `success.delivered` | `notification.endpoint.accepted` with delivered metadata | Strong endpoint evidence, not human proof |
|
||
| undelivered | `fail.delivery_failed` | `notification.endpoint.rejected_permanent` or temporary | Use reason code |
|
||
| failed | `fail.delivery_failed` | `notification.endpoint.rejected_permanent` | Strong final failure |
|
||
| rejected | `fail.carrier_rejected` | `notification.endpoint.rejected_permanent` | Strong final failure |
|
||
| expired | `fail.expired` or `undef.expired_unknown` | `notification.endpoint.unknown` or rejected | Depends on reason |
|
||
| unknown | `undef.dlr_unknown` | `notification.endpoint.unknown` | Unclear outcome |
|
||
| inbound reply | `success.reply_received` | `interaction.reply_received` | Strong awareness evidence |
|
||
| ACK reply | `success.acknowledgement_reply_received` | `interaction.acknowledgement_recorded` | Result evidence if policy accepts |
|
||
| STOP | `fail.opted_out` | `notification.channel.unsubscribe_received` | Channel constraint |
|
||
| START | active/unknown | `notification.channel.suppression_removed` | Channel may be usable |
|
||
| tracked link clicked | `undef.link_clicked_identity_uncertain` | `interaction.unverified_actor_interaction` | Identity uncertain |
|
||
|
||
### 12.2 Coordination Undef Subclasses
|
||
|
||
`coordination-engine` may derive these participant uncertainty classes from SMS evidence:
|
||
|
||
```text
|
||
undef.pending
|
||
undef.technical_acceptance_only
|
||
undef.endpoint_acceptance_only
|
||
undef.delivered_but_awareness_unproven
|
||
undef.no_signal
|
||
undef.weak_positive
|
||
undef.identity_uncertain
|
||
undef.channel_suspicious
|
||
undef.conflicting_evidence
|
||
undef.delivery_pending
|
||
undef.escalation_required
|
||
```
|
||
|
||
SMS evidence commonly produces:
|
||
|
||
```text
|
||
undef.pending
|
||
undef.endpoint_acceptance_only
|
||
undef.delivered_but_awareness_unproven
|
||
undef.identity_uncertain
|
||
undef.conflicting_evidence
|
||
```
|
||
|
||
## 13. Evidence Grading Rules
|
||
|
||
### 13.1 Provider Acceptance
|
||
|
||
```yaml
|
||
event_type: notification.attempt.accepted_by_provider
|
||
evidence_grade:
|
||
strength: weak
|
||
actor_certainty: none
|
||
authority_certainty: none
|
||
payload_certainty: low
|
||
interaction_certainty: none
|
||
timing_certainty: medium
|
||
channel_certainty: medium
|
||
non_repudiation_strength: none
|
||
notes:
|
||
- Provider accepted message for processing.
|
||
- Does not prove carrier acceptance or handset delivery.
|
||
```
|
||
|
||
### 13.2 Carrier Acceptance
|
||
|
||
```yaml
|
||
event_type: notification.endpoint.accepted
|
||
evidence_grade:
|
||
strength: medium
|
||
actor_certainty: none
|
||
authority_certainty: none
|
||
payload_certainty: low
|
||
interaction_certainty: none
|
||
timing_certainty: medium
|
||
channel_certainty: medium
|
||
non_repudiation_strength: low
|
||
notes:
|
||
- Downstream carrier or network accepted the message.
|
||
- Does not prove recipient read or understood the message.
|
||
```
|
||
|
||
### 13.3 Delivered DLR
|
||
|
||
```yaml
|
||
event_type: notification.endpoint.accepted
|
||
evidence_grade:
|
||
strength: strong
|
||
actor_certainty: none
|
||
authority_certainty: none
|
||
payload_certainty: medium
|
||
interaction_certainty: low
|
||
timing_certainty: high
|
||
channel_certainty: high
|
||
non_repudiation_strength: low
|
||
notes:
|
||
- Delivery receipt indicates successful delivery according to provider/carrier semantics.
|
||
- Does not prove human awareness or intended-recipient identity.
|
||
```
|
||
|
||
### 13.4 Undelivered / Failed
|
||
|
||
```yaml
|
||
event_type: notification.endpoint.rejected_permanent
|
||
evidence_grade:
|
||
strength: negative
|
||
actor_certainty: none
|
||
authority_certainty: none
|
||
payload_certainty: none
|
||
interaction_certainty: none
|
||
timing_certainty: medium
|
||
channel_certainty: high
|
||
non_repudiation_strength: low
|
||
notes:
|
||
- Strong evidence that the SMS channel failed for this attempt.
|
||
- Failure reason should determine retry, fallback, or suppression.
|
||
```
|
||
|
||
### 13.5 Buffered / Deferred
|
||
|
||
```yaml
|
||
event_type: notification.endpoint.deferred
|
||
evidence_grade:
|
||
strength: ambiguous
|
||
actor_certainty: none
|
||
authority_certainty: none
|
||
payload_certainty: none
|
||
interaction_certainty: none
|
||
timing_certainty: medium
|
||
channel_certainty: medium
|
||
non_repudiation_strength: none
|
||
notes:
|
||
- Message is pending or held for later delivery.
|
||
- Outcome remains uncertain until final DLR, timeout, or expiry.
|
||
```
|
||
|
||
### 13.6 Unknown / No DLR
|
||
|
||
```yaml
|
||
event_type: notification.endpoint.unknown
|
||
evidence_grade:
|
||
strength: ambiguous
|
||
actor_certainty: none
|
||
authority_certainty: none
|
||
payload_certainty: none
|
||
interaction_certainty: none
|
||
timing_certainty: low
|
||
channel_certainty: low
|
||
non_repudiation_strength: none
|
||
notes:
|
||
- No useful delivery status is available.
|
||
- Policy should decide wait, retry, alternate channel, or manual review.
|
||
```
|
||
|
||
### 13.7 Inbound Reply
|
||
|
||
```yaml
|
||
event_type: interaction.reply_received
|
||
evidence_grade:
|
||
strength: strong
|
||
actor_certainty: medium
|
||
authority_certainty: low
|
||
payload_certainty: medium
|
||
interaction_certainty: high
|
||
timing_certainty: high
|
||
channel_certainty: high
|
||
non_repudiation_strength: low
|
||
notes:
|
||
- Reply indicates interaction from the phone endpoint.
|
||
- Identity and authority may still require validation.
|
||
```
|
||
|
||
### 13.8 Acknowledgement Reply
|
||
|
||
```yaml
|
||
event_type: interaction.acknowledgement_recorded
|
||
evidence_grade:
|
||
strength: strong
|
||
actor_certainty: medium
|
||
authority_certainty: low
|
||
payload_certainty: medium
|
||
interaction_certainty: high
|
||
timing_certainty: high
|
||
channel_certainty: high
|
||
non_repudiation_strength: low
|
||
notes:
|
||
- Recipient endpoint replied with an acknowledgement keyword.
|
||
- Whether this satisfies the intended result is scenario-policy dependent.
|
||
```
|
||
|
||
### 13.9 Opt-Out
|
||
|
||
```yaml
|
||
event_type: notification.channel.unsubscribe_received
|
||
evidence_grade:
|
||
strength: negative
|
||
actor_certainty: medium
|
||
authority_certainty: low
|
||
payload_certainty: none
|
||
interaction_certainty: high
|
||
timing_certainty: high
|
||
channel_certainty: high
|
||
non_repudiation_strength: low
|
||
notes:
|
||
- Phone endpoint opted out or requested no further messages.
|
||
- Future use of the channel may be legally, contractually, or operationally constrained.
|
||
```
|
||
|
||
### 13.10 Link Click
|
||
|
||
```yaml
|
||
event_type: interaction.unverified_actor_interaction
|
||
evidence_grade:
|
||
strength: medium
|
||
actor_certainty: low
|
||
authority_certainty: none
|
||
payload_certainty: medium
|
||
interaction_certainty: medium
|
||
timing_certainty: medium
|
||
channel_certainty: medium
|
||
non_repudiation_strength: none
|
||
notes:
|
||
- Link was clicked, but actor identity is not proven.
|
||
- Stronger evidence should come from the action surface or identity adapter.
|
||
```
|
||
|
||
## 14. Message Timeline API
|
||
|
||
`sms-connect` SHOULD expose a message timeline suitable for standalone diagnostics and coordination audit.
|
||
|
||
```yaml
|
||
SmsMessageTimeline:
|
||
sms_message_id: string
|
||
sms_attempts:
|
||
- sms_attempt_id: string
|
||
provider_name: string
|
||
provider_message_id: string?
|
||
events:
|
||
- SmsTimelineEvent
|
||
segments:
|
||
- SmsSegment
|
||
current_assessment: SmsEvidenceAssessment
|
||
```
|
||
|
||
```yaml
|
||
SmsTimelineEvent:
|
||
event_id: string
|
||
event_type: string
|
||
occurred_at: timestamp
|
||
source: adapter | provider | carrier | inbound_sms | tracking | operator
|
||
native_event_type: string?
|
||
normalized_event_type: string?
|
||
summary: string
|
||
evidence_grade: EvidenceGrade
|
||
raw_event_ref: string?
|
||
```
|
||
|
||
## 15. Adapter Descriptor
|
||
|
||
`sms-connect` MUST expose an `AdapterDescriptor` compatible with AdapterInterfaceSpecification.md v1.0.
|
||
|
||
```yaml
|
||
adapter_id: sms-connect.default
|
||
adapter_name: sms-connect
|
||
adapter_version: 1.0.0
|
||
adapter_contract_version: 1.0
|
||
adapter_types:
|
||
- notification
|
||
- communication
|
||
- interaction
|
||
provider_family: sms
|
||
provider_name: configurable
|
||
deployment_mode: external
|
||
supported_channels:
|
||
- sms
|
||
supported_endpoint_types:
|
||
- phone_number
|
||
- short_code
|
||
- long_code
|
||
- toll_free
|
||
- alphanumeric_sender_id
|
||
supported_actions:
|
||
- action_type: notification.send
|
||
mode: async
|
||
idempotency_required: true
|
||
- action_type: notification.send_reminder
|
||
mode: async
|
||
idempotency_required: true
|
||
- action_type: notification.schedule
|
||
mode: scheduled
|
||
idempotency_required: true
|
||
- action_type: notification.register_tracking_context
|
||
mode: sync
|
||
idempotency_required: true
|
||
- action_type: recipient.suppress
|
||
mode: sync
|
||
idempotency_required: true
|
||
- action_type: recipient.unsuppress
|
||
mode: sync
|
||
idempotency_required: true
|
||
emitted_event_types:
|
||
- notification.attempt.created
|
||
- notification.attempt.accepted_by_adapter
|
||
- notification.attempt.rejected_by_adapter
|
||
- notification.attempt.accepted_by_provider
|
||
- notification.attempt.rejected_by_provider
|
||
- notification.attempt.queued
|
||
- notification.attempt.scheduled
|
||
- notification.attempt.delayed
|
||
- notification.endpoint.accepted
|
||
- notification.endpoint.deferred
|
||
- notification.endpoint.rejected_temporary
|
||
- notification.endpoint.rejected_permanent
|
||
- notification.endpoint.unreachable
|
||
- notification.endpoint.unknown
|
||
- notification.channel.unsubscribe_received
|
||
- notification.channel.suppression_added
|
||
- notification.channel.suppression_removed
|
||
- notification.channel.reputation_warning
|
||
- interaction.reply_received
|
||
- interaction.acknowledgement_recorded
|
||
- interaction.unverified_actor_interaction
|
||
- system.provider.degraded
|
||
- system.provider.unavailable
|
||
evidence_profile:
|
||
strongest_evidence_level: medium_to_strong
|
||
can_prove_human_awareness: false
|
||
can_prove_payload_delivery: false
|
||
can_prove_identity: false
|
||
identity_profile:
|
||
identity_strength: low_to_medium
|
||
authority_strength: none_to_low
|
||
limitations:
|
||
- SMS delivery receipts do not prove that the intended human read the message.
|
||
- Some routes do not provide reliable final delivery receipts.
|
||
- Delivered status semantics differ by provider, country, carrier, and route.
|
||
- Phone numbers may be reassigned, shared, forwarded, or unavailable.
|
||
- Link clicks do not prove actor identity.
|
||
- SMS content may be filtered by carriers or blocked by compliance rules.
|
||
- Alphanumeric sender IDs may not support inbound replies.
|
||
```
|
||
|
||
## 16. Action Request Handling
|
||
|
||
### 16.1 `notification.send`
|
||
|
||
`notification.send` sends a new SMS notification.
|
||
|
||
Required fields:
|
||
|
||
```text
|
||
request_id
|
||
action_type
|
||
coordination_case_id
|
||
participant_id
|
||
target_endpoint
|
||
content or template_ref
|
||
tracking_context
|
||
idempotency_key
|
||
requested_at
|
||
```
|
||
|
||
Example:
|
||
|
||
```yaml
|
||
request_id: req_001
|
||
action_type: notification.send
|
||
coordination_case_id: case_123
|
||
participant_id: participant_456
|
||
channel: sms
|
||
target_endpoint:
|
||
endpoint_type: phone_number
|
||
value: "+491701234567"
|
||
template_ref: secure-document-sms-reminder
|
||
variables:
|
||
portal_link: https://portal.example.com/access/abc
|
||
tracking_context:
|
||
correlation_id: corr_789
|
||
coordination_case_id: case_123
|
||
participant_id: participant_456
|
||
notification_id: notif_001
|
||
payload_id: payload_777
|
||
idempotency_key: case_123:participant_456:sms:notif_001
|
||
requested_at: 2026-01-01T12:00:00Z
|
||
```
|
||
|
||
### 16.2 Action Result
|
||
|
||
The adapter returns:
|
||
|
||
```yaml
|
||
request_id: req_001
|
||
adapter_id: sms-connect.default
|
||
accepted: true
|
||
action_state: accepted
|
||
adapter_operation_id: smsop_001
|
||
provider_operation_id: providerop_001
|
||
initial_events:
|
||
- event_type: notification.attempt.accepted_by_adapter
|
||
received_at: 2026-01-01T12:00:01Z
|
||
```
|
||
|
||
The result does not prove the SMS was delivered or read.
|
||
|
||
## 17. Provider Abstraction
|
||
|
||
`sms-connect` SHOULD support a provider abstraction layer.
|
||
|
||
Provider integration responsibilities:
|
||
|
||
* send SMS messages
|
||
* store provider message IDs
|
||
* verify webhooks
|
||
* ingest delivery receipts
|
||
* normalize provider statuses
|
||
* parse error codes
|
||
* handle opt-out events
|
||
* handle inbound replies
|
||
* expose provider health
|
||
* expose route/country/sender limitations
|
||
|
||
Provider model:
|
||
|
||
```yaml
|
||
SmsProvider:
|
||
provider_name: string
|
||
provider_account_ref: string
|
||
supported_features:
|
||
- sending
|
||
- delivery_receipts
|
||
- inbound_sms
|
||
- opt_out_handling
|
||
- sender_pools
|
||
- short_codes
|
||
- toll_free
|
||
- alphanumeric_sender_id
|
||
- scheduled_send
|
||
- status_callbacks
|
||
event_mapping_ref: string
|
||
configuration_ref: string
|
||
```
|
||
|
||
The first implementation MAY use a simulated provider. Real providers SHOULD be added behind this abstraction.
|
||
|
||
## 18. Native Provider Event Mapping
|
||
|
||
The adapter MUST support provider-specific mapping files or code modules.
|
||
|
||
Common provider-native status groups:
|
||
|
||
```text
|
||
accepted
|
||
queued
|
||
scheduled
|
||
sending
|
||
sent
|
||
dispatched
|
||
carrier_accepted
|
||
buffered
|
||
delivered
|
||
undelivered
|
||
failed
|
||
rejected
|
||
expired
|
||
unknown
|
||
deleted
|
||
opted_out
|
||
inbound
|
||
```
|
||
|
||
Important mapping rule:
|
||
|
||
Provider `delivered` events MUST be interpreted carefully.
|
||
|
||
For SMS, `delivered` usually means a provider or downstream network reports successful delivery according to its route-specific semantics. It SHOULD map to:
|
||
|
||
```text
|
||
notification.endpoint.accepted
|
||
```
|
||
|
||
with delivery metadata and strong endpoint evidence, but not to coordination result success unless the scenario policy explicitly accepts SMS DLR as sufficient.
|
||
|
||
## 19. Delivery Receipt Classification
|
||
|
||
`sms-connect` SHOULD classify delivery receipts into structured outcomes.
|
||
|
||
```yaml
|
||
SmsDeliveryReceipt:
|
||
provider_name: string
|
||
provider_message_id: string
|
||
provider_status: string
|
||
provider_error_code: string?
|
||
provider_error_text: string?
|
||
carrier_status: string?
|
||
carrier_error_code: string?
|
||
occurred_at: timestamp
|
||
final: boolean?
|
||
retryable: boolean?
|
||
classification:
|
||
- delivered
|
||
- accepted
|
||
- buffered
|
||
- deferred
|
||
- expired
|
||
- failed
|
||
- rejected
|
||
- unknown
|
||
- unknown_subscriber
|
||
- unavailable_subscriber
|
||
- unreachable_subscriber
|
||
- blocked
|
||
- filtered
|
||
- invalid_number
|
||
- route_unavailable
|
||
- sender_not_allowed
|
||
- content_not_allowed
|
||
- regulatory_rejected
|
||
- temporary_network_error
|
||
- provider_error
|
||
```
|
||
|
||
Suggested mappings:
|
||
|
||
| DLR class | SMS assessment | Normalized event |
|
||
| ----------------------- | ------------------------------------------------- | ----------------------------------------------------------------- |
|
||
| delivered | `success.delivered` | `notification.endpoint.accepted` |
|
||
| accepted | `undef.carrier_accepted_only` | `notification.endpoint.accepted` |
|
||
| buffered | `undef.buffered` | `notification.endpoint.deferred` |
|
||
| deferred | `undef.deferred` | `notification.endpoint.deferred` |
|
||
| expired | `fail.expired` or `undef.expired_unknown` | `notification.endpoint.unknown` or rejected |
|
||
| failed | `fail.delivery_failed` | `notification.endpoint.rejected_permanent` |
|
||
| rejected | `fail.carrier_rejected` | `notification.endpoint.rejected_permanent` |
|
||
| unknown | `undef.dlr_unknown` | `notification.endpoint.unknown` |
|
||
| unknown_subscriber | `fail.unknown_subscriber` | `notification.endpoint.rejected_permanent` |
|
||
| unavailable_subscriber | `undef.deferred` or `fail.unavailable_subscriber` | temporary or permanent by provider code |
|
||
| filtered | `fail.filtered_by_carrier` | `notification.endpoint.rejected_permanent` |
|
||
| sender_not_allowed | `fail.sender_not_allowed` | `notification.attempt.rejected_by_provider` or endpoint rejection |
|
||
| regulatory_rejected | `fail.policy_rejected` | `notification.endpoint.rejected_permanent` |
|
||
| temporary_network_error | `undef.deferred` | `notification.endpoint.rejected_temporary` |
|
||
|
||
## 20. Consent and Suppression Model
|
||
|
||
`sms-connect` SHOULD maintain or integrate with a consent and suppression model.
|
||
|
||
```yaml
|
||
SmsConsent:
|
||
endpoint_ref: EndpointRef
|
||
consent_state: opted_in | opted_out | unknown | not_required | disputed
|
||
consent_scope: global | tenant | sender_identity | campaign | coordination_case | purpose
|
||
consent_source: form | sms_keyword | contract | import | operator | external_system | unknown
|
||
consent_acquired_at: timestamp?
|
||
consent_evidence_ref: string?
|
||
expires_at: timestamp?
|
||
```
|
||
|
||
```yaml
|
||
SmsSuppression:
|
||
endpoint_ref: EndpointRef
|
||
suppression_type: opt_out | hard_failure | complaint | manual | provider_policy | consent_missing | unknown
|
||
scope: global | tenant | sender_identity | campaign | coordination_case | purpose
|
||
reason: string?
|
||
source: provider | adapter | operator | participant | policy
|
||
created_at: timestamp
|
||
expires_at: timestamp?
|
||
```
|
||
|
||
Suppression should produce evidence:
|
||
|
||
```text
|
||
notification.channel.suppression_added
|
||
notification.channel.suppression_removed
|
||
notification.channel.unsubscribe_received
|
||
```
|
||
|
||
coordination-engine decides whether suppression means participant failure, channel failure, alternate channel selection, or manual review.
|
||
|
||
## 21. Sender Identity and Campaign Model
|
||
|
||
SMS deliverability and compliance often depend on sender identity and campaign registration.
|
||
|
||
`sms-connect` SHOULD support sender identity metadata.
|
||
|
||
```yaml
|
||
SmsSenderIdentity:
|
||
sender_identity_id: string
|
||
sender_type: long_code | short_code | toll_free | alphanumeric_sender_id | sender_pool | unknown
|
||
sender_value: string?
|
||
country_scope:
|
||
- string
|
||
registration_state: unknown | not_required | pending | registered | rejected | expired
|
||
use_case: transactional | informational | marketing | verification | alert | mixed | unknown
|
||
brand_ref: string?
|
||
campaign_ref: string?
|
||
provider_account_ref: string?
|
||
throughput_profile_ref: string?
|
||
limitations:
|
||
- string
|
||
```
|
||
|
||
Examples of limitations:
|
||
|
||
```text
|
||
inbound_replies_not_supported
|
||
country_not_supported
|
||
marketing_not_allowed
|
||
registration_required
|
||
low_throughput
|
||
content_filtering_risk
|
||
```
|
||
|
||
## 22. Message Content, Encoding, and Segmentation
|
||
|
||
`sms-connect` SHOULD analyze message content before sending.
|
||
|
||
```yaml
|
||
SmsContentAnalysis:
|
||
body_length: integer
|
||
encoding: gsm7 | ucs2 | binary | unknown
|
||
segment_count: integer
|
||
per_segment_limit: integer
|
||
contains_url: boolean
|
||
contains_tracking_link: boolean
|
||
contains_opt_out_text: boolean
|
||
contains_non_gsm_characters: boolean
|
||
estimated_cost_units: number?
|
||
warnings:
|
||
- string
|
||
```
|
||
|
||
Potential warnings:
|
||
|
||
```text
|
||
message_requires_multiple_segments
|
||
unicode_reduces_segment_capacity
|
||
tracking_link_may_increase_length
|
||
message_missing_required_opt_out_text
|
||
content_filtering_risk
|
||
sender_identity_not_allowed_for_replies
|
||
```
|
||
|
||
Content analysis should not by itself determine coordination success, but it is important for cost, reliability, compliance, and diagnostics.
|
||
|
||
## 23. Link Tracking Model
|
||
|
||
SMS has no native open tracking. Link tracking may provide interaction evidence if a tracked URL is included.
|
||
|
||
```yaml
|
||
SmsLinkClickEvent:
|
||
sms_message_id: string
|
||
link_id: string
|
||
occurred_at: timestamp
|
||
url: string
|
||
ip_address: string?
|
||
user_agent: string?
|
||
scanner_classification: none | suspected | likely | confirmed | unknown
|
||
confidence: low | medium | high
|
||
```
|
||
|
||
Possible classifications:
|
||
|
||
```text
|
||
human_like_click
|
||
scanner_click
|
||
link_preview_fetch
|
||
unverified_click
|
||
unknown_click
|
||
```
|
||
|
||
The adapter SHOULD classify link clicks conservatively.
|
||
|
||
A click is not identity-bound unless paired with trusted identity evidence from the action surface.
|
||
|
||
## 24. Inbound Reply Model
|
||
|
||
`sms-connect` SHOULD support inbound SMS where the sender type and route support replies.
|
||
|
||
```yaml
|
||
SmsInboundMessage:
|
||
inbound_message_id: string
|
||
provider_name: string
|
||
provider_message_id: string?
|
||
from_endpoint: SmsEndpoint
|
||
to_endpoint: SmsEndpoint
|
||
received_at: timestamp
|
||
body_text: string
|
||
keyword_classification: none | opt_out | opt_in | help | acknowledgement | free_text | unknown
|
||
correlation: CorrelationContext?
|
||
raw_event_ref: string?
|
||
```
|
||
|
||
Keyword classifications:
|
||
|
||
```text
|
||
STOP / UNSUBSCRIBE / CANCEL / END / QUIT
|
||
START / UNSTOP
|
||
HELP / INFO
|
||
YES / OK / ACK / CONFIRM
|
||
NO / DECLINE
|
||
```
|
||
|
||
Actual keyword handling should be configurable by country, provider, language, sender identity, and scenario.
|
||
|
||
## 25. Phone Endpoint Quality
|
||
|
||
`sms-connect` SHOULD maintain phone endpoint quality signals.
|
||
|
||
```yaml
|
||
SmsEndpointQuality:
|
||
endpoint_ref: EndpointRef
|
||
e164_valid: boolean?
|
||
country_code: string?
|
||
number_type: mobile | landline | voip | toll_free | unknown
|
||
reachable: boolean?
|
||
carrier_name: string?
|
||
ported: boolean?
|
||
roaming: boolean?
|
||
consent_state: string?
|
||
suppression_state: string?
|
||
last_provider_acceptance_at: timestamp?
|
||
last_carrier_acceptance_at: timestamp?
|
||
last_delivered_at: timestamp?
|
||
last_failed_at: timestamp?
|
||
last_opt_out_at: timestamp?
|
||
last_reply_at: timestamp?
|
||
```
|
||
|
||
Endpoint quality may be emitted as diagnostics but should not by itself create coordination success.
|
||
|
||
## 26. Channel Health
|
||
|
||
`sms-connect` SHOULD expose channel health.
|
||
|
||
```yaml
|
||
SmsChannelHealth:
|
||
sender_identity_ref: string
|
||
provider_account_ref: string?
|
||
country_scope: string?
|
||
status: healthy | degraded | failing | unknown
|
||
registration_status: registered | pending | rejected | expired | not_required | unknown
|
||
route_status: healthy | degraded | unavailable | unknown
|
||
deliverability_status: good | warning | poor | unknown
|
||
opt_out_rate: number?
|
||
failure_rate: number?
|
||
unknown_rate: number?
|
||
filtering_rate: number?
|
||
provider_degradation:
|
||
- string
|
||
```
|
||
|
||
Health-related normalized events:
|
||
|
||
```text
|
||
notification.channel.reputation_warning
|
||
system.provider.degraded
|
||
system.provider.unavailable
|
||
system.adapter.health_changed
|
||
```
|
||
|
||
## 27. Security Requirements
|
||
|
||
`sms-connect` MUST:
|
||
|
||
* protect provider credentials
|
||
* verify provider webhooks where possible
|
||
* validate inbound provider event authenticity where possible
|
||
* preserve idempotency
|
||
* prevent duplicate sends for repeated idempotency keys
|
||
* protect opt-out and consent records
|
||
* avoid leaking phone numbers in logs where possible
|
||
* avoid exposing tracking tokens unnecessarily
|
||
* sanitize inbound SMS bodies before processing
|
||
* support tenant or sender separation where applicable
|
||
|
||
## 28. Privacy Requirements
|
||
|
||
`sms-connect` SHOULD:
|
||
|
||
* store message content only when necessary
|
||
* support metadata-only mode
|
||
* support raw event redaction
|
||
* support configurable retention of raw provider events
|
||
* store endpoint references instead of full phone numbers where possible
|
||
* support masking of phone numbers
|
||
* support deletion or anonymization workflows
|
||
* separate operational diagnostics from coordination evidence
|
||
* avoid unnecessary retention of link-click metadata
|
||
* allow link tracking to be disabled
|
||
|
||
## 29. Reliability Requirements
|
||
|
||
`sms-connect` MUST support:
|
||
|
||
* idempotent send requests
|
||
* duplicate webhook event detection
|
||
* out-of-order event handling
|
||
* late DLR handling
|
||
* retryable provider failures
|
||
* non-retryable provider failures
|
||
* provider timeout handling
|
||
* correlation preservation
|
||
* dead-letter handling for unprocessable events
|
||
* explicit timeout/expiry handling for missing final DLRs
|
||
|
||
Late events MUST be preserved.
|
||
|
||
Example:
|
||
|
||
```text
|
||
Provider accepted at 10:00
|
||
No DLR by 12:00
|
||
Participant unresolved at 14:00
|
||
Delivery receipt arrives at 18:00
|
||
```
|
||
|
||
The late DLR must still be recorded and emitted as evidence.
|
||
|
||
## 30. Raw Event Preservation
|
||
|
||
`sms-connect` SHOULD preserve raw provider events or references to them.
|
||
|
||
```yaml
|
||
RawSmsEventRef:
|
||
raw_event_id: string
|
||
provider_name: string
|
||
storage_ref: string?
|
||
received_at: timestamp
|
||
redacted: boolean
|
||
```
|
||
|
||
Normalized events should reference raw event data where available:
|
||
|
||
```yaml
|
||
raw_event_ref: raw_sms_event_123
|
||
```
|
||
|
||
## 31. Minimal API Surface
|
||
|
||
`sms-connect` SHOULD expose a headless API.
|
||
|
||
### 31.1 Adapter Contract API
|
||
|
||
Required conceptual operations:
|
||
|
||
```text
|
||
GET /adapter/descriptor
|
||
GET /adapter/health
|
||
POST /adapter/actions
|
||
POST /adapter/events/provider
|
||
GET /adapter/events
|
||
GET /adapter/messages/{id}/timeline
|
||
GET /adapter/messages/{id}/assessment
|
||
```
|
||
|
||
The actual transport may differ, but these conceptual operations should exist.
|
||
|
||
### 31.2 Standalone API
|
||
|
||
Useful standalone operations:
|
||
|
||
```text
|
||
POST /sms/send
|
||
GET /sms/messages/{id}
|
||
GET /sms/messages/{id}/timeline
|
||
GET /sms/messages/{id}/assessment
|
||
GET /sms/endpoints/{id}/quality
|
||
POST /sms/suppressions
|
||
DELETE /sms/suppressions/{id}
|
||
POST /sms/consents
|
||
GET /sms/channel-health
|
||
GET /sms/content/analyze
|
||
```
|
||
|
||
## 32. Example End-to-End Flows
|
||
|
||
### 32.1 Secure Document Reminder
|
||
|
||
1. `coordination-engine` creates a coordination case.
|
||
2. Email notification remains unresolved after threshold.
|
||
3. Policy engine selects SMS fallback.
|
||
4. `coordination-engine` sends `notification.send` to `sms-connect`.
|
||
5. `sms-connect` validates consent, suppression, sender identity, content, and segmentation.
|
||
6. Provider accepts the SMS.
|
||
7. `sms-connect` emits `notification.attempt.accepted_by_provider`.
|
||
8. Carrier reports delivery.
|
||
9. `sms-connect` emits `notification.endpoint.accepted` with delivered metadata.
|
||
10. Recipient clicks link.
|
||
11. `sms-connect` emits `interaction.unverified_actor_interaction`.
|
||
12. Recipient authenticates in portal.
|
||
13. `portal-connect` emits `identity.actor_authenticated`.
|
||
14. Recipient downloads document.
|
||
15. `portal-connect` emits `delivery.payload.downloaded`.
|
||
16. `coordination-engine` marks participant complete.
|
||
|
||
SMS contributed stronger notification evidence than email, but the portal event closes the result.
|
||
|
||
### 32.2 Undelivered SMS and Alternate Channel
|
||
|
||
1. `sms-connect` sends SMS.
|
||
2. Provider accepts the message.
|
||
3. Carrier returns unknown subscriber.
|
||
4. `sms-connect` emits `notification.endpoint.rejected_permanent`.
|
||
5. `coordination-engine` marks SMS channel failed for that participant.
|
||
6. Policy engine selects alternate channel or manual review.
|
||
|
||
### 32.3 SMS Acknowledgement
|
||
|
||
1. `coordination-engine` sends alert via SMS.
|
||
2. Carrier reports delivery.
|
||
3. Participant replies `ACK`.
|
||
4. `sms-connect` emits `interaction.acknowledgement_recorded`.
|
||
5. If scenario policy accepts SMS acknowledgement, `coordination-engine` marks the participant as acknowledged.
|
||
6. If stronger identity is required, the participant remains incomplete until identity-bound evidence is provided.
|
||
|
||
### 32.4 Opt-Out Handling
|
||
|
||
1. Participant replies `STOP`.
|
||
2. `sms-connect` records opt-out and adds suppression.
|
||
3. `sms-connect` emits `notification.channel.unsubscribe_received` and `notification.channel.suppression_added`.
|
||
4. `coordination-engine` updates channel viability.
|
||
5. Policy engine selects fallback channel or manual review depending on scenario.
|
||
|
||
## 33. Provider Implementation Guidance
|
||
|
||
The first real provider integration SHOULD be selected based on:
|
||
|
||
* delivery receipt quality
|
||
* clear status callbacks
|
||
* error-code detail
|
||
* inbound SMS support
|
||
* opt-out handling support
|
||
* sender identity support
|
||
* country coverage
|
||
* webhook security
|
||
* correlation metadata support
|
||
* cost and local availability
|
||
* compatibility with transactional and high-assurance notifications
|
||
|
||
The implementation SHOULD avoid hardcoding provider semantics into the core SMS model.
|
||
|
||
Provider-specific modules should map to the SMS-native model first, then to normalized coordination events.
|
||
|
||
```text
|
||
Provider event
|
||
→ sms-native event
|
||
→ SmsEvidenceAssessment
|
||
→ EvidenceEvent for coordination-engine
|
||
```
|
||
|
||
## 34. Message Stream Separation
|
||
|
||
`sms-connect` SHOULD support sender identities, campaign scopes, and message streams.
|
||
|
||
Recommended streams:
|
||
|
||
```text
|
||
transactional
|
||
notification
|
||
legal_or_high_assurance_notice
|
||
marketing
|
||
verification
|
||
system_alert
|
||
test
|
||
```
|
||
|
||
High-assurance or legally relevant notifications SHOULD NOT share th sender configuration with marketing traffic unless explicitly accepted by policy.
|
||
|
||
Stream separation may affect:
|
||
|
||
* sender number
|
||
* sender pool
|
||
* short code
|
||
* toll-free number
|
||
* alphanumeric sender ID
|
||
* campaign registration
|
||
* consent scope
|
||
* opt-out scope
|
||
* throughput
|
||
* carrier filtering risk
|
||
* delivery diagnostics
|
||
|
||
## 35. Legal and Compliance Disclaimer
|
||
|
||
`sms-connect` does not by itself provide legal proof of delivery, legal notice, acceptance, signature, payment, or contract closure.
|
||
|
||
It provides evidence from the SMS channel.
|
||
|
||
Scenario-specific applications and `coordination-engine` policies may combine SMS evidence with stronger evidence from portal, identity, signature, payment, archive, or manual processes.
|
||
|
||
The adapter MUST avoid naming technical SMS events in ways that imply legal success.
|
||
|
||
Use:
|
||
|
||
```text
|
||
notification.endpoint.accepted
|
||
```
|
||
|
||
Avoid:
|
||
|
||
```text
|
||
recipient_legally_notified
|
||
delivery_legally_completed
|
||
human_read_confirmed
|
||
```
|
||
|
||
## 36. MVP Scope
|
||
|
||
The first useful version of `sms-connect` should implement:
|
||
|
||
1. Adapter descriptor.
|
||
2. Adapter health endpoint.
|
||
3. `notification.send`.
|
||
4. Idempotent send request handling.
|
||
5. Simulated provider or one real provider.
|
||
6. SMS message and attempt records.
|
||
7. Provider event ingestion.
|
||
8. Basic delivery receipt classification.
|
||
9. Basic phone endpoint validation.
|
||
10. Basic segmentation and encoding analysis.
|
||
11. Basic opt-out/suppression support.
|
||
12. Basic inbound reply support if provider allows.
|
||
13. Evidence event generation.
|
||
14. Message timeline.
|
||
15. Message assessment.
|
||
16. Mapping to AdapterInterfaceSpecification.md v1.0.
|
||
|
||
### MVP Required SMS Events
|
||
|
||
```text
|
||
notification.attempt.accepted_by_adapter
|
||
notification.attempt.rejected_by_adapter
|
||
notification.attempt.accepted_by_provider
|
||
notification.attempt.rejected_by_provider
|
||
notification.attempt.queued
|
||
notification.endpoint.accepted
|
||
notification.endpoint.deferred
|
||
notification.endpoint.rejected_temporary
|
||
notification.endpoint.rejected_permanent
|
||
notification.endpoint.unknown
|
||
notification.channel.unsubscribe_received
|
||
notification.channel.suppression_added
|
||
interaction.reply_received
|
||
interaction.acknowledgement_recorded
|
||
interaction.unverified_actor_interaction
|
||
```
|
||
|
||
### MVP Acceptance Criteria
|
||
|
||
The MVP is acceptable when it can:
|
||
|
||
1. Accept a coordination-compatible SMS send request.
|
||
2. Dispatch or simulate an SMS.
|
||
3. Preserve correlation and idempotency.
|
||
4. Ingest or simulate provider delivery receipts.
|
||
5. Produce normalized evidence events.
|
||
6. Classify provider acceptance as weak evidence.
|
||
7. Classify delivered DLR as strong endpoint evidence but not human-awareness proof.
|
||
8. Classify final undelivered/failed/rejected states as negative channel evidence.
|
||
9. Classify unknown/no-DLR cases as unresolved.
|
||
10. Record STOP/opt-out as suppression evidence.
|
||
11. Record inbound replies as interaction evidence.
|
||
12. Provide a message timeline.
|
||
13. Provide an SMS evidence assessment.
|
||
14. Integrate with coordination-engine without overclaiming success.
|
||
|
||
## 37. Future Extensions
|
||
|
||
Potential future capabilities:
|
||
|
||
* multi-provider routing
|
||
* provider failover
|
||
* country-specific routing rules
|
||
* sender pool optimization
|
||
* throughput management
|
||
* advanced number lookup
|
||
* number reassignment risk detection
|
||
* HLR or carrier lookup integration where lawful and available
|
||
* advanced opt-in/opt-out workflow
|
||
* advanced keyword routing
|
||
* two-way conversational SMS support
|
||
* natural-language reply classification
|
||
* URL shortening and tracking integration
|
||
* adaptive fallback between email, SMS, push, and voice
|
||
* RCS adapter extension or separate `rcs-connect`
|
||
* MMS support
|
||
* WhatsApp or messaging-app adapters
|
||
* regulatory diagnostics by country
|
||
* A2P registration diagnostics
|
||
* carrier filtering risk scoring
|
||
* cost forecasting by segment count and country
|
||
* consent evidence archive integration
|
||
|
||
## 38. Non-Goals
|
||
|
||
`sms-connect` is not:
|
||
|
||
* a marketing automation platform
|
||
* a full conversational messaging platform
|
||
* a CRM
|
||
* a full workflow engine
|
||
* a legal notice system by itself
|
||
* a payment system
|
||
* a signature system
|
||
* an identity verification system by itself
|
||
* the owner of coordination case success
|
||
|
||
It may integrate with such systems or be used by them.
|
||
|
||
## 39. Summary
|
||
|
||
`sms-connect` models SMS as a useful but still uncertain notification, reminder, and lightweight interaction channel.
|
||
|
||
Its job is to:
|
||
|
||
* send SMS messages
|
||
* ingest provider and carrier delivery receipts
|
||
* classify SMS outcomes
|
||
* normalize SMS evidence
|
||
* manage opt-out and suppression state
|
||
* record inbound replies
|
||
* analyze content, encoding, and segmentation
|
||
* expose endpoint and channel diagnostics
|
||
* integrate cleanly with `coordination-engine`
|
||
|
||
The key rule is:
|
||
|
||
> SMS events are evidence, not result satisfaction. sms-connect reports SMS-channel facts and uncertainty. coordination-engine evaluates intended results.
|
||
|
||
[1]: https://help.twilio.com/articles/223134347-What-are-the-Possible-SMS-and-MMS-Message-Statuses-and-What-do-They-Mean-?utm_source=chatgpt.com "What are the possible SMS and MMS message statuses ..."
|
||
[2]: https://developer.vonage.com/en/messaging/sms/guides/delivery-receipts?utm_source=chatgpt.com "SMS Delivery Receipts API Guide"
|
||
[3]: https://developers.sinch.com/docs/sms/api-reference/sms/delivery-reports/deliveryreport?utm_source=chatgpt.com "Delivery Report"
|
||
[4]: https://www.twilio.com/docs/glossary/what-sms-character-limit?utm_source=chatgpt.com "How long can a message be? | Twilio"
|
||
[5]: https://www.twilio.com/docs/messaging/tutorials/advanced-opt-out?utm_source=chatgpt.com "Customize users' opt-in and opt-out experience with ..."
|
||
|