> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getdialed.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# SMS

> Send a text message through Twilio from a number you own — with recipients validated before anything is sent, sends paced by the dispatcher, quiet hours enforced per recipient, and a reply that can pause the flow until a human answers

The `twilio__sms` service sends **text messages**: the one-time passcode, the appointment reminder, the short question a person is expected to answer. Two steps ship, both authored on the same shared REST foundation every other integration uses, and both authenticated by selecting a [credential](/concepts/credentials) rather than by pasting a key into a step.

```json theme={null}
{
  "task_id": "task_notify",
  "task_name": "Text the customer their appointment time",
  "platform_id": "twilio",
  "service_id": "twilio__sms",
  "action_id": "twilio__sms__send",
  "connection_id": "cred_8d1a18c4",
  "parameters": {
    "to": "{{ input.record.phone_e164 }}",
    "sender": "+15551234567",
    "body": "Your appointment is confirmed for {{ input.record.appointment_time }}."
  }
}
```

<Warning>
  **Text messages to US numbers are gated by law, not by preference.** A `marketing` message is held until the recipient's own local sending window is open, and `marketing` is what a step gets when it says nothing — the example above included. A step that genuinely sends something the recipient asked for and is waiting on declares `traffic_class: "transactional"`, which an administrator must authorise. Read [quiet hours](/concepts/quiet-hours) before you author your first send: a held message is the single most likely reason a text you expected to go out immediately did not.
</Warning>

## The two steps

| Action                       | What it does                                                                                                                                                                 |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `twilio__sms__send`          | Sends one text and returns as soon as Twilio accepts it for sending.                                                                                                         |
| `twilio__sms__send_and_wait` | Sends one text and then **pauses the flow** until the recipient texts back or the step's timeout runs out. See [human in the loop](/concepts/human-in-the-loop#sms-replies). |

A send addresses **one** recipient. A flow that texts a list does it by running the step per record, which is what keeps one bad number from failing everybody else's message — and what lets the quiet-hours gate hold one recipient without holding the rest.

There is no media step and no media parameter in either direction: these steps send text and receive text. An inbound picture message is not silently dropped into your flow as an empty reply — media is simply out of scope for this service.

### What a send takes

| Parameter | Required | What it is                                                                                                                                      |
| --------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `to`      | yes      | The number to text, **already in E.164 form** — see [recipients](#recipients).                                                                  |
| `sender`  | yes      | Who the message comes from: one of your own numbers in E.164 form, **or** a Messaging Service SID. See [both sender forms](#both-sender-forms). |
| `body`    | yes      | The text, up to the 1,600 characters Twilio accepts in one message. Longer text is refused before the message is sent.                          |

Anything over a single segment — 160 characters of plain GSM text, or 70 if the text contains an emoji, a curly quote or any other non-GSM character — is split by the carrier and **billed per segment**. The step reports how many segments were used.

### What a send reports

| Output                | What it holds                                                                                                                                               |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `provider_message_id` | Twilio's identifier for the accepted message, beginning with `SM`. Keep it: it is how you correlate this send with what Twilio reports about it afterwards. |
| `status`              | Always `queued`. It means **Twilio has taken the message for sending** — never that a handset received it.                                                  |
| `num_segments`        | How many segments the text was split into, as Twilio counted them. Each one is billed separately.                                                           |

<Note>
  **No output field can carry a phone number, and that is structural rather than a convention.** Step outputs are persisted into the platform's run history, which outlives whatever retention rule governed the record the number came from — a number there could never be redacted afterwards. Twilio echoes both numbers back on a successful send and again on every webhook; those are read here only to produce the three fields above. The same rule governs failure messages: a refusal names the reason and the provider's error code, never the number.
</Note>

## Recipients

The `to` value must **already** be a plus sign, a country code and a national number, with no spaces, dashes or brackets: `+15551234567`. This step performs no normalization and guesses no country. Anything else is refused as `RECIPIENT_NOT_E164` **before any request leaves**, so a malformed number costs no message, no money and no pacing budget.

That strictness is deliberate. A step that guessed would have to guess a country, and a wrong guess is a message to a stranger.

**So normalize before the send, not inside it.** The `toE164` formatter exists for exactly this and belongs in a [record transform](/concepts/record-transforms#the-row-reference) step, where it runs over the whole record set and reports each unnormalizable row by name while the rest carry on:

```json theme={null}
{
  "action_id": "getdialed__records__map_fields",
  "parameters": {
    "record_set_id": "{{ step_upload.output.record_set_id }}",
    "field_map": {
      "phone_e164": "{{ row.phone_number | trim | toE164 }}"
    }
  }
}
```

The send step then references the normalized column — `"to": "{{ input.record.phone_e164 }}"` — and every number reaching it is already in the shape it requires.

<Warning>
  **Do not chain `| toE164` inside the send step's own `to` expression.** A send step's parameters are resolved by the flow engine rather than by the step, and the phone-number formatter cannot run there: a task written that way does not fail cleanly — it stalls, with no error on the run and no message sent. Normalize the value in a record transform first, as above, or supply an already-E.164 value.

  This is a current limitation and it is being fixed. Until it is, treat the send step's `to` as a plain reference to a value that is already correct.
</Warning>

## Both sender forms

`sender` accepts either of the two forms Twilio itself models, and the step works out which one you gave from its shape:

| Form                    | Looks like     | When to use it                                                                                                                                                                                                    |
| ----------------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| A phone number you own  | `+15551234567` | You want the message to come from one specific number. **Required** for `send_and_wait`.                                                                                                                          |
| A Messaging Service SID | `MG…`          | You want Twilio to pick a number from that service's sender pool. This is how a registered 10DLC campaign is usually wired, and it is what [delivery outcomes](#acceptance-is-not-delivery) are reported through. |

Exactly one form is supplied per message. Anything that is neither is refused as `SENDER_FORM_UNRECOGNIZED` before the message is sent.

The choice is not purely stylistic — the two forms differ in what comes back afterwards:

|                                | E.164 number | Messaging Service |
| ------------------------------ | ------------ | ----------------- |
| Can be used by `send_and_wait` | **yes**      | no — refused      |
| Inbound replies arrive         | yes          | yes               |
| Delivery outcomes arrive       | **no**       | yes               |

<Warning>
  **A Messaging Service cannot be used for `send_and_wait`.** The service chooses a number from its pool at the moment the message goes out, so the reply comes back from a number the platform could never have registered a wait against — and the flow would sit until its timeout on an answer the person really gave. A reply-waiting step configured with a Messaging Service is refused **before anything is sent**, by name. Name one of your own numbers instead.
</Warning>

## Acceptance is not delivery

**A successful send means Twilio accepted the message. It does not mean anyone received it.** This is the one thing on this page most worth internalising, because every ordinary surface reads clean: the step succeeds, the execution reaches `completed`, and the batch reaches `completed` — for a message a carrier may have dropped seconds later.

Delivery is reported **afterwards**, by Twilio, on a status callback that the platform records against the Job that sent the message: the provider's final status (`delivered`, `undelivered` or `failed`), its error code where it names one, and when that answer last moved. The record only ever moves forward, so a late `sent` arriving behind a `failed` cannot walk the truth backwards.

<Warning>
  **Delivery outcomes only arrive for messages sent through a Messaging Service.** The callback is registered on the Messaging Service itself; a Twilio phone number has no equivalent setting, and a send step deliberately cannot supply a callback URL of its own — a URL a step could set would be one nobody could verify a signature against, and it would put a secret into flow definitions.

  So a send whose `sender` is a bare E.164 number still receives **inbound replies** — those arrive on the number — but produces **no delivery outcome at all**. Its delivery status stays empty, which means *no answer was ever reported*, not *delivered* and not *failed*.

  If delivery truth matters to a flow, send it through a Messaging Service.
</Warning>

Two practical consequences:

* **Do not read `completed` as "delivered"** on a dashboard, in a downstream flow, or in a report you hand to someone else. It means *accepted for sending* — and for a bare-number send there may be nothing further to know.
* **A reply-waiting step acts on a delivery failure when it gets one.** A `failed` or `undelivered` outcome ends the wait immediately with a named reason, rather than parking the flow for hours on a person who was never texted — but see the sender caveat in [when the message never arrives](/concepts/human-in-the-loop#when-the-message-never-arrives), because a reply-waiting step cannot use a Messaging Service.

<Note>
  **The recorded delivery outcome is not yet published on the jobs API.** It is written and it is monotonic; what is missing is a read surface. Until one ships, keep `provider_message_id` from the step's output — it is the handle that finds the message in your own Twilio account's message log, where the same answer is visible whichever sender form you used.
</Note>

## When a send is refused

Every unsuccessful send fails with a **named** reason rather than a status number, because the remedies are completely different:

| Name                           | What happened                                                                                                                                      | What to do                                                                                                      |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `RECIPIENT_NOT_E164`           | The number was not in E.164 form. Refused before any request left.                                                                                 | Normalize it upstream — see [recipients](#recipients).                                                          |
| `BODY_TOO_LONG`                | The text ran past the 1,600 characters Twilio accepts. Refused before any request left.                                                            | Shorten it, or split it into two sends.                                                                         |
| `SENDER_FORM_UNRECOGNIZED`     | `sender` was neither an E.164 number nor a Messaging Service SID.                                                                                  | Fix the value.                                                                                                  |
| `RECIPIENT_SUPPRESSED`         | The recipient has replied **STOP** and Twilio will not deliver to them.                                                                            | Nothing in a flow can override it. Only the recipient can, by replying START.                                   |
| `RECIPIENT_INVALID`            | Correctly formatted, but Twilio cannot text it — the number does not exist, is not a mobile line, or is in a region your account cannot reach.     | Check the record.                                                                                               |
| `CAMPAIGN_UNREGISTERED`        | US carriers require the sending number to be registered to an approved 10DLC campaign, and Twilio reports this one is not. Twilio's error `30034`. | Complete the registration in **your own** Twilio account — see [registration is yours](#registration-is-yours). |
| `PROVIDER_ERROR`               | Twilio refused the message for a reason this step has no specific name for.                                                                        | The provider's code is in the message; your Twilio account's message log carries the detail.                    |
| `PROVIDER_RESPONSE_UNREADABLE` | Twilio answered and the answer could not be read. **The message may already have gone out.**                                                       | Check your Twilio message log before sending again.                                                             |

Every one of these is **non-retryable**, and none of them repeats the number involved.

<Note>
  **`30034` is permanent, not transient.** It fails *every* message from that number until the campaign is approved, so a flow that sees it is not having a bad minute — it is telling you a registration is missing. It also arrives in two shapes: as a refusal on the send when Twilio catches it up front, and as an `undelivered` delivery outcome afterwards when the carrier is the one that rejects it.
</Note>

## Connecting Twilio

The credential is a `basic_auth` credential carrying your Twilio **Account SID** and your **account auth token** — the pair Twilio shows on its console dashboard:

```json theme={null}
{
  "name": "Twilio Production",
  "platform_id": "twilio",
  "auth_method": "basic_auth",
  "credentials": {
    "account_sid": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "auth_token": "REPLACE_ME"
  }
}
```

GetDialed calls Twilio with the pair, reads back which **Account** it belongs to, and files the credential under that account — you never look up or type a provider identifier, and a credential that cannot authenticate never becomes a record. See [platform tenancy](/concepts/platform-tenancy) for what that record owns.

<Warning>
  **It must be the account auth token, not an API Key secret.** A Twilio API Key — the credential whose identifier starts with `SK` — sends messages perfectly well, so the mistake is invisible on the send path. What it cannot do is verify the signature Twilio puts on inbound replies and delivery callbacks, because Twilio signs those with the account auth token and nothing else. The symptom is that replies never resume a waiting flow and delivery outcomes never arrive, which reads like a routing bug and is not one.
</Warning>

<Note>
  **Twilio subaccounts each produce their own record.** A subaccount carries its own Account SID, so a customer running one subaccount per brand gets one tenant record per subaccount. That is correct rather than a duplicate: each subaccount has its own throughput budget, and metering two independent budgets against one ledger would over-send on both.
</Note>

## Registration is yours

<Warning>
  **Customer SMS is bring-your-own registration.**

  To send SMS to US phone numbers through a GetDialed flow, you supply your own SMS provider credentials **and** your own registered A2P 10DLC brand and campaign. GetDialed does not register 10DLC on your behalf. Unregistered US A2P traffic is blocked by the carriers, and when that happens your provider returns that error directly — GetDialed surfaces the provider's response unchanged.
</Warning>

Concretely, before your first US send: register the brand and the campaign in your own Twilio account, and attach the sending number or the Messaging Service to the approved campaign. Until that is done, sends from that number fail with `CAMPAIGN_UNREGISTERED` or come back `undelivered` with error `30034`, and nothing on this platform can change that.

Opt-outs are Twilio's too. **STOP** handling is provider-native, so a recipient who opts out is refused by Twilio on the next send and surfaced here as `RECIPIENT_SUPPRESSED`. There is no parameter, flag or override that reaches past it, and there is no separate GetDialed suppression list to keep in step.

## Pacing, and sending exactly once

**Sends are paced, and the pacing is the dispatcher's job rather than the step's.** Every message draws from the `SMS` rate bucket on your Twilio [Account record](/concepts/platform-tenancy#rate-buckets-and-effective-limits), spends it in the `interactive` [lane](/concepts/dispatch-pacing#lane-order-and-the-starvation-floor), and waits rather than failing when the budget is spent. A text is user-visible latency — somebody is waiting on the passcode — which is why it sits in the lane reserved for work a person is watching, and never behind a bulk record load.

SMS is also the clearest case for the **[minimum interval](/concepts/dispatch-pacing#the-minimum-interval)**: what a long-code A2P sender is really constrained by is the gap between two consecutive messages, not a per-hour count. The catalog seeds a deliberately conservative floor and the live, per-account value lives on the Account record.

<Warning>
  **A send is never retried automatically.** Both steps declare `retry_safety: at_most_once` in the [catalog](/concepts/catalog#what-each-level-tells-you), which routes them to a single-attempt policy: if the answer to a send is lost in transit, the platform does **not** send again to find out.

  **Twilio publishes no idempotency key**, so there is no second line of defence at the provider's end — the single-attempt declaration is the whole of the guarantee. That is the right trade here: a duplicate text cannot be recalled, it is charged for twice, and sending one to a consumer who did not ask for it is a compliance incident rather than a cosmetic defect.
</Warning>

## Next steps

<CardGroup cols={2}>
  <Card title="Quiet hours" icon="moon" href="/concepts/quiet-hours">
    When a text may legally be sent, how the recipient's clock is worked out, and what a held message means.
  </Card>

  <Card title="Human in the loop" icon="user-check" href="/concepts/human-in-the-loop#sms-replies">
    Pause a flow until somebody texts back, and how a reply is matched to the step that asked.
  </Card>

  <Card title="Credentials" icon="key" href="/concepts/credentials">
    Create the Twilio credential these steps select.
  </Card>

  <Card title="Dispatch pacing" icon="gauge-high" href="/concepts/dispatch-pacing">
    How sends are paced against your account's budget, and what a pause holds.
  </Card>
</CardGroup>
