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

> ## Agent Instructions
> Install the Courier skill before writing code: `npx skills add trycourier/courier-skills`. It carries the verified SDK shapes and the rules you cannot get wrong.
> Authenticate every request with `Authorization: Bearer <API_KEY>`. A workspace has several environments and each has its own keys, which are plain `pk_` strings with no environment prefix. Start with Test.
> Send with `client.send.message`, the default import of the v7 Node SDK. Reference a template by its `nt_` id or its alias.
> A send accepts a bare Elemental element list, but storing content on a template requires the top-level elements wrapped in a channel element.
> Templates and journeys can be built in the Courier app or created through the API. Either way they live in the workspace and are referenced by ID when you send.
> The hosted MCP server is https://mcp.courier.com. For a briefing on what Courier is and when to use it, read https://www.courier.com/llms.txt.
> Prefer the Guides tab for how-do-I questions and the Docs tab for how-does-it-behave questions. The API reference lives under /api-reference.

# Send SMS with Twilio through Courier

> Connect Twilio with an Account SID, Auth Token, and Messaging Service SID or from number.

export const Doc = ({href, children, name, bare}) => {
  const label = children || name || href;
  if (bare) {
    return <a href={href}>{label}</a>;
  }
  return <a className="cx-endpoint" data-kind="doc" href={href}>
      <span className="cx-endpoint-label">{label}</span>
      <span className="cx-endpoint-method">DOC</span>
    </a>;
};

export const AppLink = ({href, children, name, bare}) => {
  const label = children || name || "Open in Courier";
  if (bare) {
    return <a href={href} target="_blank" rel="noreferrer">{label}</a>;
  }
  return <a className="cx-endpoint" data-kind="app" href={href} target="_blank" rel="noreferrer">
      <span className="cx-endpoint-label">{label}</span>
      <span className="cx-endpoint-method" aria-hidden="true">↗</span>
    </a>;
};

## Prerequisites

* [A Twilio account](https://www.twilio.com/try-twilio)
* [An SMS-capable Twilio phone number](https://www.twilio.com/docs/phone-numbers)
* [A Messaging Service containing that phone number](https://www.twilio.com/docs/sms/send-messages#messaging-services)
* [Your Twilio Account SID and Auth Token](https://www.twilio.com/console/project/settings)
* Your Twilio Messaging Service SID

## Setup

<Steps>
  <Step title="Get your Twilio credentials">
    In Twilio, open the [console](https://console.twilio.com/) and copy your Account SID and Auth Token. The Messaging Service SID is on the Messaging Services page.
  </Step>

  <Step title="Configure in Courier">
    Open the <AppLink href="https://app.courier.com/integrations/twilio">Twilio Integration</AppLink> in Courier, enter your Account SID, Auth Token, and Messaging Service SID, then click "Save."
  </Step>
</Steps>

Addressing a recipient and sending are the same on every SMS provider, so they are documented once: <Doc href="/docs/integrations/sms/overview#profile-requirements">profile requirements</Doc> and <Doc href="/docs/integrations/sms/overview#send-to-a-recipient">send to a recipient</Doc>.

## Trial accounts

<Note>
  Twilio trial accounts may only send to verified numbers. See the [Twilio trial account guide](https://www.twilio.com/docs/usage/tutorials/how-to-use-your-free-trial-account).
</Note>

## Overrides

<Doc href="/docs/send/overrides#how-overrides-work">How overrides work</Doc> covers the two levels and which one wins.

Overrides change the request body Courier sends to Twilio. You can override any field on Twilio's `Messages.json` endpoint, listed in the [Twilio message resource reference](https://www.twilio.com/docs/sms/api/message-resource#create-a-message-resource).

<Warning>
  Courier polls Twilio's API for delivery status using the credentials you configured. Swapping those credentials with a `config` override stops delivery status events.
</Warning>

<CodeGroup>
  ```javascript Node.js highlight={11-22} theme={null}
  const { requestId } = await courier.send.message({
    message: {
      template: "nt_01kx4h2jdafq8bk9aftxak4b40",
      to: {
        phone_number: "+12345678901",
      },
      data: {
        name: "Sarah Bennett",
      },
      providers: {
        twilio: {
          override: {
            body: {
              To: "+10987654321",
            },
            config: {
              accountSid: "<your Account SID>",
              authToken: "<your Auth Token>",
              messagingServiceSid: "<your Messaging Service SID>",
            },
          },
        },
      },
    },
  });
  ```

  ```python Python highlight={11-22} theme={null}
  response = client.send.message(
      message={
          "template": "nt_01kx4h2jdafq8bk9aftxak4b40",
          "to": {
              "phone_number": "+12345678901",
          },
          "data": {
              "name": "Sarah Bennett",
          },
          "providers": {
              "twilio": {
                  "override": {
                      "body": {
                          "To": "+10987654321",
                      },
                      "config": {
                          "accountSid": "<your Account SID>",
                          "authToken": "<your Auth Token>",
                          "messagingServiceSid": "<your Messaging Service SID>",
                      },
                  },
              },
          },
      },
  )
  ```

  ```bash cURL highlight={14-25} wrap theme={null}
  curl -X POST https://api.courier.com/send \
    -H "Authorization: Bearer $COURIER_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "message": {
        "template": "nt_01kx4h2jdafq8bk9aftxak4b40",
        "to": {
          "phone_number": "+12345678901"
        },
        "data": {
          "name": "Sarah Bennett"
        },
        "providers": {
          "twilio": {
            "override": {
              "body": {
                "To": "+10987654321"
              },
              "config": {
                "accountSid": "<your Account SID>",
                "authToken": "<your Auth Token>",
                "messagingServiceSid": "<your Messaging Service SID>"
              }
            }
          }
        }
      }
    }'
  ```

  ```ruby Ruby highlight={11-22} theme={null}
  response = courier.send_.message(
    message: {
      template: "nt_01kx4h2jdafq8bk9aftxak4b40",
      to: {
        phone_number: "+12345678901"
      },
      data: {
        name: "Sarah Bennett"
      },
      providers: {
        twilio: {
          override: {
            body: {
              To: "+10987654321"
            },
            config: {
              accountSid: "<your Account SID>",
              authToken: "<your Auth Token>",
              messagingServiceSid: "<your Messaging Service SID>"
            }
          }
        }
      }
    }
  )
  ```

  ```go Go highlight={13-24} theme={null}
  response, err := client.Send.Message(context.TODO(), courier.SendMessageParams{
  	Message: courier.SendMessageParamsMessage{
  		To: courier.SendMessageParamsMessageToUnion{
  			OfUserRecipient: &shared.UserRecipientParam{
  				PhoneNumber: courier.String("+12345678901"),
  			},
  		},
  		Template: courier.String("nt_01kx4h2jdafq8bk9aftxak4b40"),
  		Data: map[string]any{
  			"name": "Sarah Bennett",
  		},
  		Providers: shared.MessageProvidersParam{
  			"twilio": shared.MessageProvidersTypeParam{
  				Override: map[string]any{
  					"body": map[string]any{
  						"To": "+10987654321",
  					},
  					"config": map[string]any{
  						"accountSid": "<your Account SID>",
  						"authToken": "<your Auth Token>",
  						"messagingServiceSid": "<your Messaging Service SID>",
  					},
  				},
  			},
  		},
  	},
  })
  ```

  ```java Java highlight={9-18} theme={null}
  SendMessageParams params = SendMessageParams.builder()
      .message(SendMessageParams.Message.builder()
          .to(UserRecipient.builder().phoneNumber("+12345678901").build())
          .template("nt_01kx4h2jdafq8bk9aftxak4b40")
          .data(JsonValue.from(java.util.Map.of(
              "name", "Sarah Bennett"
          )))
          .providers(MessageProviders.builder()
              .putAdditionalProperty("twilio", JsonValue.from(java.util.Map.of("override", java.util.Map.of(
                      "body", java.util.Map.of(
                          "To", "+10987654321"
                      ),
                      "config", java.util.Map.of(
                          "accountSid", "<your Account SID>",
                          "authToken", "<your Auth Token>",
                          "messagingServiceSid", "<your Messaging Service SID>"
                      )
                  ))))
              .build())
          .build())
      .build();
  SendMessageResponse response = client.send().message(params);
  ```

  ```php PHP highlight={11-22} theme={null}
  $response = $client->send->message(
    message: [
      'template' => 'nt_01kx4h2jdafq8bk9aftxak4b40',
      'to' => [
        'phone_number' => '+12345678901',
      ],
      'data' => [
        'name' => 'Sarah Bennett',
      ],
      'providers' => [
        'twilio' => [
          'override' => [
            'body' => [
              'To' => '+10987654321',
            ],
            'config' => [
              'accountSid' => '<your Account SID>',
              'authToken' => '<your Auth Token>',
              'messagingServiceSid' => '<your Messaging Service SID>',
            ],
          ],
        ],
      ],
    ],
  );
  ```

  ```csharp C# highlight={13-32} theme={null}
  SendMessageParams parameters = new()
  {
      Message = new()
      {
          To = new UserRecipient { PhoneNumber = "+12345678901" },
          Template = "nt_01kx4h2jdafq8bk9aftxak4b40",
          Data = new Dictionary<string, JsonElement>()
          {
              { "name", JsonSerializer.SerializeToElement("Sarah Bennett") },
          },
          Providers = new Dictionary<string, MessageProvidersType>()
          {
              {
                  "twilio",
                  new()
                  {
                      Override = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
                          """
                          {
                            "body": {
                              "To": "+10987654321"
                            },
                            "config": {
                              "accountSid": "<your Account SID>",
                              "authToken": "<your Auth Token>",
                              "messagingServiceSid": "<your Messaging Service SID>"
                            }
                          }
                          """
                      ),
                  }
              },
          },
      },
  };

  var response = await client.Send.Message(parameters);
  ```

  ```bash CLI highlight={6} wrap theme={null}
  courier send message \
    --api-key "$COURIER_API_KEY" \
    --message.to '{"phone_number": "+12345678901"}' \
    --message.template nt_01kx4h2jdafq8bk9aftxak4b40 \
    --message.data '{"name": "Sarah Bennett"}' \
    --message.providers '{"twilio": {"override": {"body": {"To": "+10987654321"}, "config": {"accountSid": "<your Account SID>", "authToken": "<your Auth Token>", "messagingServiceSid": "<your Messaging Service SID>"}}}}'
  ```

  ```text MCP theme={null}
  With Courier MCP, send my template to +12345678901 through Twilio with different credentials.
  ```
</CodeGroup>

<Note>
  The Twilio API uses PascalCase field names (`Body`, `To`, `From`). Courier auto-capitalizes `body` override keys for backwards compatibility, so both `"to"` and `"To"` work.
</Note>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Error 30003: Destination Unavailable">
    The destination handset was unreachable.

    * Send another test message via the Twilio API Explorer
    * Check device status, signal strength, and carrier network
    * Use the "Fallback to Long Code" feature if needed
    * Open a Twilio support request if the issue persists
  </Accordion>

  <Accordion title="Error 30005: Unknown or Inactive Destination">
    Carrier issue, signal loss, or inactive number.

    * Verify the number is active and formatted correctly (E.164)
    * Check device status and signal
    * Test with other devices on the same carrier
  </Accordion>

  <Accordion title="Error 30006: Landline or Unreachable Carrier">
    The message went to a landline or an unreachable carrier.

    * Use the [Lookup API](https://www.twilio.com/docs/lookup/v2-api) to check if the number is a landline
    * Try an alternative phone number
  </Accordion>

  <Accordion title="Error 30007: Message Filtered">
    Twilio or the carrier filtered the message for a policy violation.

    * Ensure compliance with Twilio's Messaging and Acceptable Use Policies
    * Secure account credentials
    * Open a support request if you suspect a mistake
  </Accordion>

  <Accordion title="Error 30008: Unknown Delivery Failure">
    The message failed for unknown reasons.

    * Test with a simpler message and a different sender ID
    * Check device roaming status
    * Contact Twilio support with recent message SIDs if the issue persists
  </Accordion>

  <Accordion title="Error 20003: Permission Denied">
    No permission to access the requested resource.

    * Verify the correct Auth Token and Account SID combination
    * Check account type (sub-account vs master, test vs live)
    * Confirm API Key and Auth Token are valid
  </Accordion>

  <Accordion title="Error 20404: Resource Not Found">
    The resource doesn't exist or isn't available.

    * Check that the resource exists and verify API case-sensitivity
    * Verify Account SID and base URL
  </Accordion>

  <Accordion title="Error 21211: Invalid To Phone Number">
    The recipient number is invalid or formatted incorrectly.

    * Ensure E.164 format with the correct country code
    * Avoid calling or messaging a Twilio number from itself
  </Accordion>

  <Accordion title="Error 21408: Permission Not Enabled for Region">
    * Enable permission in [Geo-Permissions settings](https://www.twilio.com/console/sms/settings/geo-permissions)
  </Accordion>

  <Accordion title="Error 21610: SMS STOP Filter">
    The recipient unsubscribed with the "STOP" keyword.

    * Request the recipient text "START" to resubscribe
    * Ensure you have consent for messaging
  </Accordion>

  <Accordion title="Error 11200: HTTP Retrieval Failure">
    Twilio could not retrieve content from the given URL.

    * Verify web server status and accessibility
    * Check for network issues
    * Ensure proper server configuration for static resources
  </Accordion>

  <Accordion title="Error 12300: Invalid Content-Type">
    Twilio could not process the URL's Content-Type.

    * Verify the correct Content-Type is returned by the server
    * Ensure the URL refers to a valid, acceptable resource
  </Accordion>
</AccordionGroup>

## Provider details

```text theme={null}
twilio
```

Courier recommends routing to the channel. Naming this key in `routing.channels` instead is supported, and sends through just this provider.

<Card title="Send to a specific provider" icon="bullseye-arrow" href="/docs/send/send-to-a-provider" horizontal arrow="true">
  When that is worth doing, and what you give up: failover, channel priority, and providers you add later.
</Card>
