64 KiB
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) 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) 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) SMS content length, encoding, and concatenation matter because GSM-7, UCS-2, and multi-segment messages affect cost, delivery behavior, and diagnostics. (Twilio) 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)
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:
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.sendnotification.send_remindernotification.schedulenotification.cancelwhere technically possible before dispatchrecipient.suppressrecipient.unsuppressrecipient.opt_in_recordrecipient.opt_out_recordnotification.register_tracking_context
Signals
notification.attempt.accepted_by_adapternotification.attempt.rejected_by_adapternotification.attempt.accepted_by_providernotification.attempt.rejected_by_providernotification.attempt.queuednotification.attempt.schedulednotification.attempt.delayednotification.endpoint.acceptednotification.endpoint.deferrednotification.endpoint.rejected_temporarynotification.endpoint.rejected_permanentnotification.endpoint.unreachablenotification.endpoint.unknownnotification.channel.suppression_addednotification.channel.suppression_removednotification.channel.complaint_receivednotification.channel.unsubscribe_receivednotification.channel.reputation_warninginteraction.reply_receivedinteraction.acknowledgement_recordedinteraction.unverified_actor_interactioninteraction.authenticated_actor_interactiononly when paired with trusted identity evidencesystem.provider.degradedsystem.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:
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.
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.
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.
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.
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.
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:
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.
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:
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:
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:
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:
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:
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
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
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
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
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
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
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
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
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
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
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.
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
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.
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:
request_id
action_type
coordination_case_id
participant_id
target_endpoint
content or template_ref
tracking_context
idempotency_key
requested_at
Example:
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:
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:
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:
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:
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.
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.
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?
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:
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.
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:
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.
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:
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.
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:
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.
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:
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.
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.
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:
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:
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.
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:
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:
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:
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
coordination-enginecreates a coordination case.- Email notification remains unresolved after threshold.
- Policy engine selects SMS fallback.
coordination-enginesendsnotification.sendtosms-connect.sms-connectvalidates consent, suppression, sender identity, content, and segmentation.- Provider accepts the SMS.
sms-connectemitsnotification.attempt.accepted_by_provider.- Carrier reports delivery.
sms-connectemitsnotification.endpoint.acceptedwith delivered metadata.- Recipient clicks link.
sms-connectemitsinteraction.unverified_actor_interaction.- Recipient authenticates in portal.
portal-connectemitsidentity.actor_authenticated.- Recipient downloads document.
portal-connectemitsdelivery.payload.downloaded.coordination-enginemarks participant complete.
SMS contributed stronger notification evidence than email, but the portal event closes the result.
32.2 Undelivered SMS and Alternate Channel
sms-connectsends SMS.- Provider accepts the message.
- Carrier returns unknown subscriber.
sms-connectemitsnotification.endpoint.rejected_permanent.coordination-enginemarks SMS channel failed for that participant.- Policy engine selects alternate channel or manual review.
32.3 SMS Acknowledgement
coordination-enginesends alert via SMS.- Carrier reports delivery.
- Participant replies
ACK. sms-connectemitsinteraction.acknowledgement_recorded.- If scenario policy accepts SMS acknowledgement,
coordination-enginemarks the participant as acknowledged. - If stronger identity is required, the participant remains incomplete until identity-bound evidence is provided.
32.4 Opt-Out Handling
- Participant replies
STOP. sms-connectrecords opt-out and adds suppression.sms-connectemitsnotification.channel.unsubscribe_receivedandnotification.channel.suppression_added.coordination-engineupdates channel viability.- 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.
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:
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:
notification.endpoint.accepted
Avoid:
recipient_legally_notified
delivery_legally_completed
human_read_confirmed
36. MVP Scope
The first useful version of sms-connect should implement:
- Adapter descriptor.
- Adapter health endpoint.
notification.send.- Idempotent send request handling.
- Simulated provider or one real provider.
- SMS message and attempt records.
- Provider event ingestion.
- Basic delivery receipt classification.
- Basic phone endpoint validation.
- Basic segmentation and encoding analysis.
- Basic opt-out/suppression support.
- Basic inbound reply support if provider allows.
- Evidence event generation.
- Message timeline.
- Message assessment.
- Mapping to AdapterInterfaceSpecification.md v1.0.
MVP Required SMS Events
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:
- Accept a coordination-compatible SMS send request.
- Dispatch or simulate an SMS.
- Preserve correlation and idempotency.
- Ingest or simulate provider delivery receipts.
- Produce normalized evidence events.
- Classify provider acceptance as weak evidence.
- Classify delivered DLR as strong endpoint evidence but not human-awareness proof.
- Classify final undelivered/failed/rejected states as negative channel evidence.
- Classify unknown/no-DLR cases as unresolved.
- Record STOP/opt-out as suppression evidence.
- Record inbound replies as interaction evidence.
- Provide a message timeline.
- Provide an SMS evidence assessment.
- 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.