Skip to main content

Send SMS to your users

Ory Network comes with an HTTP based SMS delivery option that can be configured to point to any service that supports sending SMS via HTTP API, such as Twilio, Plivo, AWS SNS, or your own microservice.

Configuration

SMS delivery can be configured through Ory Console or the Ory CLI. Follow these steps to configure SMS:

  1. Go to AuthenticationSMS Configuration in the Ory Console
  2. Add the configuration for your SMS provider

Body configuration

The body of the above snippet decodes to the following Jsonnet template:

function(ctx) {
To: ctx.recipient,
Body: ctx.body,
}

Fields available on the ctx object are:

  • recipient: The recipient's phone number
  • body: The message body
  • template_type: The template type, e.g. verification_code
  • template_data: The template data, e.g. { "VerificationCode": "1234", Idenity: { ... } }
  • message_type: The message type, e.g. sms
  • request_headers: A map of all HTTP request headers forwarded from the user request (see below)

Accessing request headers

The following request headers are available via ctx.request_headers:

Accept
Accept-Encoding
Accept-Language
Content-Length
Content-Type
Origin
Priority
Referer
Sec-Ch-Ua
Sec-Ch-Ua-Mobile
Sec-Ch-Ua-Platform
Sec-Fetch-Dest
Sec-Fetch-Mode
Sec-Fetch-Site
Sec-Fetch-User
True-Client-Ip
User-Agent
X-Forwarded-Host
Ory-Base-Url-Rewrite
Ory-Base-Url-Rewrite-Token
X-Ory-Original-Host
Ory-No-Custom-Domain-Redirect
Cf-Ipcountry

Read the Jsonnet documentation to learn more about the Jsonnet templating language.

Provider examples

The Configuration example above uses Twilio. The following request_config snippets show how to adapt it for other SMS providers — each replaces the url, method, headers, and auth fields of the http channel, while the rest of the channel configuration stays the same.

Twilio

url: https://api.twilio.com/2010-04-01/Accounts/{$ACCOUNT_SID}/Messages.json
method: POST
headers:
Content-Type: application/x-www-form-urlencoded # required — Twilio rejects JSON
auth:
type: basic_auth
config:
user: { $ACCOUNT_SID }
password: { $AUTH_TOKEN }

MessageBird (Bird)

url: https://rest.messagebird.com/messages
method: POST
headers:
Content-Type: application/json
Authorization: AccessKey {$MESSAGEBIRD_API_KEY}

The body Jsonnet must strip the leading + from Ory's E.164 recipient (MessageBird expects MSISDN format):

function(ctx) {
body: std.manifestJsonEx({
originator: 'YourApp',
recipients: [
if std.startsWith(ctx.recipient, '+')
then std.substr(ctx.recipient, 1, std.length(ctx.recipient) - 1)
else ctx.recipient,
],
body: ctx.body,
}, ' '),
}

Plivo

url: https://api.plivo.com/v1/Account/{$AUTH_ID}/Message/
method: POST
headers:
Content-Type: application/json
auth:
type: basic_auth
config:
user: { $AUTH_ID }
password: { $AUTH_TOKEN }

Plivo accepts E.164 directly (no + stripping). For production, use a Powerpack (number pool with intelligent routing) — replace src with powerpack_uuid in the Jsonnet body.

Sinch

# US stack
url: https://us.sms.api.sinch.com/xms/v1/<service-plan-id>/batches
# EU stack
# url: https://eu.sms.api.sinch.com/xms/v1/<service-plan-id>/batches
method: POST
headers:
authorization: Bearer {$SINCH_API_TOKEN}
content-type: application/json

Sinch enforces per-country sender registration — using the wrong sender for a region silently degrades delivery.

Vonage (Nexmo)

url: https://rest.nexmo.com/sms/json
method: POST
headers:
Content-Type: application/json
# No `auth:` block — Vonage authenticates via API key + secret in the request BODY, not headers.

Body Jsonnet:

function(ctx) {
body: std.manifestJsonEx({
api_key: '{$VONAGE_API_KEY}',
api_secret: '{$VONAGE_API_SECRET}',
from: 'YourApp',
to: ctx.recipient,
text: ctx.body,
}, ' '),
}

Vonage accepts E.164 with or without the leading +; strip the + in Jsonnet if a country rejects the + form.

WhatsApp Business

WhatsApp delivers OTPs as pre-approved template messages rather than plain text, so it needs a custom courier handler configured under courier.channels instead of courier.sms — it doesn't fit the simple request_config pattern above. See WhatsApp Business OTP courier for the full setup.

Templates

Only the recovery_code, verification_code, and login_code templates support an SMS variant. Use the CLI to configure it:

  1. Download the Ory Identities config from your project and save it to a file:

    ## List all available workspaces
    ory list workspaces

    ## List all available projects
    ory list projects --workspace <workspace-id>

    ## Get config
    ory get identity-config --project <project-id> --workspace <workspace-id> --format yaml > identity-config.yaml
  2. Add the configuration for your custom SMTP server

    config.yml
    courier:
    templates:
    verification_code:
    valid:
    sms:
    body:
    plaintext: "base64://WW91ciB2ZXJpZmljYXRpb24gY29kZSBpczoge3sgLlZlcmlmaWNhdGlvbkNvZGUgfX0="
    login_code:
    valid:
    sms:
    body:
    plaintext: "base64://WW91ciBsb2dpbiBjb2RlIGlzOiB7eyAuTG9naW5Db2RlIH19"

    recovery_code:
    valid:
    sms:
    body:
    plaintext: "base64://WW91ciByZWNvdmVyeSBjb2RlIGlzOiB7eyAuUmVjb3ZlcnlDb2RlIH19CgpAe3sgLlJlcXVlc3RVUkxEb21haW4gfX0gI3t7IC5SZWNvdmVyeUNvZGUgfX0K"
  3. Update the Ory Identities configuration using the file you worked with:

    ory update identity-config --project <project-id> --workspace <workspace-id> --file updated_config.yaml