# Send SMS

```yaml
capability_id: sms.send
display_name: Send SMS
ui_capability_key: send_sms
scope: sms.send
endpoints:
  - provider: twilio
    method: POST
    path: /twilio/sms/send
  - provider: telnyx
    method: POST
    path: /telnyx/sms/send
  - provider: aws
    method: POST
    path: /aws/sms/send
operations:
  twilio: twilio.sms.send
  telnyx: telnyx.sms.send
  aws: aws.sms.send
billable: true
environment: required
```

## Purpose and authorization

`sms.send` lets an authenticated Gateman application submit native SMS
requests through configured Twilio, Telnyx, or AWS End User Messaging SMS.
The application selects the provider by choosing its endpoint.

Every request MUST use authenticated [Contract v1](https://docs.getgateman.com/contract-v1/)
transport and the `sms.send` scope. Provider credentials are managed by
Gateman and MUST NOT be supplied by the application.

Gateman authenticates, authorizes, accounts for, validates the supported
boundary of, and relays the operation. The provider remains responsible for
native processing, routing, segmentation, carrier handling, and delivery.
Gateman does not provide a provider-neutral composer, contact-list manager,
sending queue, automatic failover, opt-out system, or handset-delivery
guarantee.

## Implementation contract

### Common request rules

All SMS routes are authenticated JSON `POST` requests. Contract v1 signing,
timestamps, raw-body handling, and authentication errors are defined by
[Contract v1](https://docs.getgateman.com/contract-v1/) and
[Authentication](https://docs.getgateman.com/authentication/).

```http
POST /twilio/sms/send
Content-Type: application/json
X-GM-App: app_example
X-GM-Ts: 1791748800
X-GM-Sig: <Contract-v1-signature>
X-GM-Idempotency-Key: order-sms-123
```

`X-GM-Idempotency-Key` is optional and is included in the Contract v1
signature when supplied. Every request MUST include `environment` with one of
`production`, `staging`, or `development`. Gateman lowercases and trims this
value, then removes it before provider execution. Direct HTTP integrations
MUST include it; official PHP, Node.js, Python, and Go clients can inject it.

Provider selection is endpoint-based. Each endpoint accepts only its listed
provider-native fields. Unsupported JSON fields are rejected. Each request
supports one recipient; recipient arrays and bulk expansion are not supported.

### Twilio: `POST /twilio/sms/send`

Supported native fields:

| Field | Contract |
| --- | --- |
| `To` | Required single E.164 recipient. |
| `From` | Required unless `MessagingServiceSid` is supplied. Accepts E.164, a 3–11 character alphanumeric sender ID, or a 3–8 digit short code. Colon-containing values are rejected. |
| `MessagingServiceSid` | Required unless `From` is supplied. Must match `MG` followed by 32 hexadecimal characters. |
| `Body` | Required, non-empty string, at most 1,600 bytes by PHP byte length. |
| `StatusCallback` | Optional HTTP/HTTPS URL with a valid host; hosts containing `_` are rejected. |

All supplied Twilio field values must be non-empty strings, at most 4,096
bytes, and must not contain CR or LF. Other fields are rejected.

```json
{
  "environment": "production",
  "To": "+15551234567",
  "From": "+15557654321",
  "Body": "Your example verification code is 482901.",
  "StatusCallback": "https://app.example.test/sms/twilio-status"
}
```

Gateman forwards a native Twilio Message create request. It does not calculate
GSM-7/UCS-2 encoding or provider segments. The 1,600-byte check is not a
provider-calculated segment count.

### Telnyx: `POST /telnyx/sms/send`

Supported native fields:

| Field | Contract |
| --- | --- |
| `to` | Required single E.164 recipient or 3–8 digit short code. |
| `from` | Optional sender; required when `messaging_profile_id` is absent. Must not contain CR/LF. |
| `messaging_profile_id` | Optional profile; required when `from` is absent. Must not contain CR/LF. |
| `text` | Required non-empty string. Gateman enforces no maximum length. |
| `webhook_url` | Optional HTTP/HTTPS URL with a valid host. |
| `webhook_failover_url` | Optional HTTP/HTTPS URL with a valid host. |
| `use_profile_webhooks` | Optional boolean. |
| `type` | Optional; if present, must be `SMS`. |
| `auto_detect` | Optional boolean. |
| `send_at` | Optional string passed to Telnyx. |
| `encoding` | Optional `auto`, `gsm7`, or `ucs2`. |

Unknown fields and invalid field types are rejected. Remaining native message,
sender, encoding, scheduling, destination, and account restrictions are
determined by Telnyx.

```json
{
  "environment": "production",
  "to": "+15551234567",
  "messaging_profile_id": "40000000-0000-0000-0000-000000000001",
  "text": "Your example verification code is 482901.",
  "type": "SMS",
  "encoding": "auto",
  "webhook_url": "https://app.example.test/sms/telnyx-status"
}
```

### AWS End User Messaging SMS: `POST /aws/sms/send`

The route invokes AWS `SendTextMessage` through the AWS SDK. Supported native
fields are:

`DestinationPhoneNumber`, `OriginationIdentity`, `MessageBody`, `MessageType`,
`Keyword`, `ConfigurationSetName`, `MaxPrice`, `TimeToLive`, `Context`,
`DestinationCountryParameters`, `DryRun`, `ProtectConfigurationId`, and
`MessageFeedbackEnabled`.

Gateman validates only that unknown fields are absent,
`DestinationPhoneNumber` is a non-empty string, and `MessageBody`—when
provided—is a string. Gateman does not require `MessageBody`, validate E.164,
enforce segment limits, validate sender identity, or validate every native AWS
parameter combination.

```json
{
  "environment": "production",
  "DestinationPhoneNumber": "+15551234567",
  "OriginationIdentity": "arn:aws:sms-voice:us-east-1:000000000000:phone-number/example",
  "MessageBody": "Your example verification code is 482901.",
  "MessageType": "TRANSACTIONAL",
  "DryRun": true
}
```

This endpoint returns a Gateman JSON envelope rather than a transparent raw
HTTP response relay.

## Environment behavior

| Environment | Execution | Credentials | Accounting |
| --- | --- | --- | --- |
| `production` | Selected provider is invoked. | Applicable provider credentials are required. | One accepted request consumes one operation. |
| `staging` | Validates, then captures locally. | Provider credentials are not required for capture. | One accepted capture consumes one operation. |
| `development` | Validates, then captures locally. | Provider credentials are not required for capture. | One accepted capture consumes one operation. |

Staging and development do not use provider sandbox APIs and do not send an
SMS. Authorized console users can inspect captures, which are retained for
seven days. There is no customer-facing SMS capture API.

```json
{
  "ok": true,
  "staged": true,
  "sent": false,
  "provider_accepted": false,
  "delivery_status": "not_sent",
  "message_id": null,
  "capture_id": "01J00000000000000000000000",
  "provider": "twilio",
  "environment": "staging"
}
```

The capture ID is only a Gateman correlation value.

## Provider responses and delivery meaning

Twilio and Telnyx production responses preserve the native provider HTTP
status and body. Gateman forwards only these selected headers when present:
`content-type`, the provider request ID (`twilio-request-id` or
`telnyx-request-id`), and `request-id`. The body is not converted into a
common SMS schema.

AWS success returns a Gateman envelope containing AWS SDK result data:

```json
{
  "ok": true,
  "result": { "MessageId": "provider-message-id-example" }
}
```

Gateman acceptance, provider API acceptance, carrier processing, and handset
receipt are distinct states. A successful response confirms local capture or
provider API acceptance only. Gateman has no production SMS ledger and no SMS
delivery-status API.

## Operation accounting, idempotency, and replay obligations

### Idempotency

The key is optional. Surrounding ASCII spaces and tabs are trimmed; an empty
key is omitted. A non-empty key must be at most 255 bytes and contain no ASCII
control characters or DEL.

The scope is:

```text
application + provider + environment + idempotency key
```

The fingerprint includes the provider, environment, and complete native
payload. A retained terminal result replays its HTTP status, body, and selected
headers without a new provider request or operation. Changed payloads return
HTTP 409 `idempotency_key_payload_conflict`. Concurrent, unresolved, or
uncertain execution returns HTTP 409 `sms_send_indeterminate`.

Pre-admission provider-concurrency, monthly-limit, and accounting rejections
release the current claim. Terminal successful and definite-failure results
are retained for 15 minutes. Indeterminate and unresolved claims remain
protected. A caller MUST NOT create a new key to bypass uncertainty unless it
deliberately accepts possible duplicate sending. Official clients do not
automatically retry provider operations.

### Accounting and limits

Billable operation types are `twilio.sms.send`, `telnyx.sms.send`, and
`aws.sms.send`. Each accepted production submission or nonproduction capture
consumes one Gateman operation. This is not one SMS segment or one provider
charge. Invalid requests, authentication failures, monthly-limit rejection,
and relevant pre-reservation concurrency rejection do not consume an
operation. Failures after reservation remain billable.

Application admission can return HTTP 429 `app_concurrency_limit_exceeded`
with `Retry-After`, or HTTP 503 `app_concurrency_guard_unavailable`.
Production provider-bound admission can return HTTP 429 `Too many concurrent
provider requests` with `Retry-After: 1`, or HTTP 503 `Provider concurrency
guard unavailable`. The provider-bound limit is two concurrent requests per
application. Monthly exhaustion returns HTTP 403
`monthly_operation_limit_reached`; accounting failure returns HTTP 503
`Operation accounting unavailable`.

An idempotent replay does not initiate another provider request or consume
another operation.

## Encoding and segmentation

Gateman does not calculate GSM-7/UCS-2 segments, split messages, expand one
request into multiple recipients, or calculate provider charges. Telnyx's
`encoding` field is passed as a native option; Twilio and AWS encoding remain
provider-native. A long or Unicode message may create multiple provider-billed
segments even though Gateman counts one accepted operation.

## Callbacks and delivery tracking

Twilio supports caller-supplied `StatusCallback`. Telnyx supports
`webhook_url`, `webhook_failover_url`, and `use_profile_webhooks`. AWS has no
Gateman-managed SMS callback receiver or tracking API.

Gateman does not receive, verify, forward, store, or correlate delivery
callbacks. Applications own callback endpoint security, provider verification,
duplicates, retries, and message-ID correlation. Captured nonproduction SMS
does not create provider callbacks. See [Webhooks and event handling](https://docs.getgateman.com/webhooks-and-event-handling/).

## Provider configuration and responsibility boundaries

Credentials are configured per application. Twilio requires an account
identity, API key identity, and API key secret; Telnyx requires an API key;
AWS SMS requires a region, access key ID, and secret access key, with an
optional session token. AWS SMS credentials are distinct from SES credentials.

Gateman does not provision Twilio senders or messaging services, Telnyx
messaging profiles, AWS SMS resources, sender registrations, or callback
infrastructure. There is no dedicated SMS readiness endpoint.

Provider and carrier requirements for sender identity, destination eligibility,
registration, throughput, content, consent, and opt-out handling remain the
application's and provider's responsibility. Gateman does not manage STOP/
START handling, suppression lists, consent records, or contacts.

## Error outcomes

| Condition | Response |
| --- | --- |
| Authentication, stale signature, or missing scope | HTTP 401 with the authentication error, including `Scope not allowed` where applicable. |
| Invalid JSON | HTTP 400 `Invalid JSON`. |
| Missing/unsupported environment | HTTP 400 `Application environment is required` or `Unsupported application environment`. |
| Gateman boundary validation failure | HTTP 400 with the provider-specific validation message. |
| Application concurrency | HTTP 429/503 `app_concurrency_limit_exceeded` or `app_concurrency_guard_unavailable`. |
| Provider concurrency | HTTP 429/503 `Too many concurrent provider requests` or `Provider concurrency guard unavailable`. |
| Monthly allowance exhausted | HTTP 403 `monthly_operation_limit_reached`. |
| Accounting unavailable | HTTP 503 `Operation accounting unavailable`. |
| Provider credentials unavailable | HTTP 503 `sms_provider_unavailable`. |
| Twilio/Telnyx provider HTTP error | Native provider status/body and selected headers. |
| Unkeyed Twilio transport/runtime failure | HTTP 502 `Twilio request failed`. |
| Unkeyed Telnyx transport/runtime failure | HTTP 502 `Telnyx request failed`. |
| Keyed uncertain Twilio/Telnyx execution | HTTP 409 `sms_send_indeterminate`. |
| AWS definite rejection | HTTP 502 with `AWS SendTextMessage rejected the request` and an AWS error code. |
| AWS uncertain execution with idempotency | HTTP 409 `sms_send_indeterminate`. |
| Idempotency payload conflict | HTTP 409 `idempotency_key_payload_conflict`. |
| Local capture failure | HTTP 503 `SMS staging capture unavailable`. |

The fixed Twilio and Telnyx transport messages are the public responses for
unexpected unkeyed runtime failures. Internal exception messages are not
public. A transport failure does not prove that no SMS was accepted.

## Retention and data handling

Production SMS bodies are not stored in a Gateman SMS ledger. Nonproduction
captures contain recipient, sender projection, message content, native
payload, provider, environment, and capture metadata and are available to
authorized console users for seven days.

Definite terminal SMS idempotency results are retained for 15 minutes.
Indeterminate and unresolved claims remain protected. Operation accounting is
separate from capture and idempotency retention. Provider-side retention is
independent of Gateman. Gateman does not provide SMS suppression, opt-out,
consent-record, or contact-list management.

## Implementation outcome

A conforming integration authenticates with Contract v1 and `sms.send`,
selects the intended provider endpoint, submits that provider's native shape
and environment, handles its response format, distinguishes provider
acceptance from handset delivery, treats nonproduction capture as
non-delivery, uses idempotency appropriately, avoids blind retries after
`sms_send_indeterminate`, accounts for one operation per accepted request,
and handles delivery callbacks independently when configured.

## Implementation instructions for agents

- An integration SHOULD use an official Gateman PHP, Node.js, Python, or Go
  client where appropriate. Direct HTTP implementations MUST conform to
  Contract v1.
- The integration MUST use `sms.send` and select exactly one of the three
  provider endpoints.
- It MUST preserve provider-native field names and structures and MUST NOT
  invent a universal SMS payload.
- It MUST supply a valid `environment` and treat nonproduction capture as
  non-delivery.
- It MUST distinguish Gateman operations from provider segmentation and
  provider charges.
- It SHOULD use a stable idempotency key where duplicate sending matters and
  MUST NOT blindly retry or create a new key after unresolved execution.
- It MUST NOT assume official clients retry automatically or that Gateman
  provides delivery tracking/callback processing.
- It MUST implement its own callback receiver and verification when using
  provider callbacks.

## Reusable AGENTS.md block

```markdown
<!-- gateman:begin capability=sms.send -->
- Send SMS with authenticated Contract v1 and the `sms.send` scope.
- Select exactly one native provider endpoint: `/twilio/sms/send`,
  `/telnyx/sms/send`, or `/aws/sms/send`.
- Include `environment`: production invokes the provider; staging/development
  capture locally and do not send an SMS.
- Handle Twilio/Telnyx native responses and the AWS `{ok, result}` envelope.
- One accepted request or capture consumes one Gateman operation; this is not
  one provider segment or provider-side charge.
- `X-GM-Idempotency-Key` is optional and scoped by application, provider,
  environment, and key. Preserve the same key and payload for replay.
- Treat `sms_send_indeterminate` as unresolved provider execution. Do not
  blindly retry or create a new key to bypass uncertainty. Official clients
  do not automatically retry.
- Gateman does not provide delivery tracking or callback processing. Applications
  using callbacks own endpoint security, verification, retries, duplicates,
  and message correlation.
- Full reference: https://docs.getgateman.com/capabilities/sms.send/
<!-- gateman:end capability=sms.send -->
```

## Related references

- [Contract v1](https://docs.getgateman.com/contract-v1/)
- [Authentication](https://docs.getgateman.com/authentication/)
- [Environments](https://docs.getgateman.com/environments/)
- [Idempotency](https://docs.getgateman.com/idempotency/)
- [Operation Accounting](https://docs.getgateman.com/operation-accounting/)
- [Rate limits and concurrency](https://docs.getgateman.com/rate-limits-and-concurrency/)
- [Errors and responses](https://docs.getgateman.com/errors-and-responses/)
- [Data retention](https://docs.getgateman.com/data-retention/)
- [Webhooks and event handling](https://docs.getgateman.com/webhooks-and-event-handling/)
- [Official PHP client](https://docs.getgateman.com/canonical-client-php/)
- [Official Node.js client](https://docs.getgateman.com/canonical-client-node/)
- [Official Python client](https://docs.getgateman.com/canonical-client-python/)
- [Official Go client](https://docs.getgateman.com/canonical-client-go/)
