RelayAPI

SMS API

Send SMS and MMS messages with RelayAPI — text messages, images, multi-recipient delivery, and media messaging via Twilio.

Quick Reference

PropertyValue
Platform keysms
Auth methodTwilio Account SID + Auth Token
Text limit1,600 characters (auto-segmented)
Media per message10 (MMS)
Image formatsJPEG, PNG, GIF
Image max size5 MB
Video formatsMP4
Video max size5 MB
Message typesSMS (text), MMS (media)
Multi-recipientYes (sends individually to each number)
SchedulingYes
AnalyticsNo (use Twilio dashboard)

SDK sourceTypeScript · Python · Go · Java · REST API

Before You Start

SMS uses Twilio as the delivery provider — you must bring your own Twilio credentials. Each message requires a from number (your Twilio phone number) and at least one recipient phone number in E.164 format (e.g., +15017122661). Messages longer than 160 characters are auto-segmented by Twilio into multiple SMS segments. MMS (media messages) supports up to 10 media URLs per message. Multi-recipient messages are sent individually to each phone number (not as a group).

Quick Start

Send an SMS message:

import Relay from '@relayapi/sdk';

const client = new Relay();

const post = await client.posts.create({
  content: 'Hello from RelayAPI!',
  targets: ['sms'],
  scheduled_at: 'now',
  target_options: {
    sms: {
      from_number: '+15017122661',
      phone_numbers: ['+14155238886']
    }
  }
});

console.log(post.id); // post_abc123
from relay import Relay

client = Relay()

post = client.posts.create(
    content='Hello from RelayAPI!',
    targets=['sms'],
    scheduled_at='now',
    target_options={
        'sms': {
            'from_number': '+15017122661',
            'phone_numbers': ['+14155238886']
        }
    }
)

print(post.id)  # post_abc123
client := relaygo.NewClient()

post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("Hello from RelayAPI!"),
    Targets:     relaygo.F([]string{"sms"}),
    ScheduledAt: relaygo.F("now"),
    TargetOptions: relaygo.F(map[string]interface{}{
        "sms": map[string]interface{}{
            "from_number":   "+15017122661",
            "phone_numbers": []string{"+14155238886"},
        },
    }),
})
RelayClient client = RelayOkHttpClient.fromEnv();

PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Hello from RelayAPI!")
    .addTarget("sms")
    .scheduledAt("now")
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("sms", JsonValue.from(Map.of(
            "from_number", "+15017122661",
            "phone_numbers", List.of("+14155238886")
        )))
        .build())
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Hello from RelayAPI!",
    "targets": ["sms"],
    "scheduled_at": "now",
    "target_options": {
      "sms": {
        "from_number": "+15017122661",
        "phone_numbers": ["+14155238886"]
      }
    }
  }'

Content Types

Text Message (SMS)

Plain text message up to 1,600 characters. Messages over 160 characters are auto-segmented.

const post = await client.posts.create({
  content: 'Your order #12345 has been shipped! Track it here: https://example.com/track/12345',
  targets: ['sms'],
  scheduled_at: 'now',
  target_options: {
    sms: {
      from_number: '+15017122661',
      phone_numbers: ['+14155238886']
    }
  }
});
post = client.posts.create(
    content='Your order #12345 has been shipped! Track it here: https://example.com/track/12345',
    targets=['sms'],
    scheduled_at='now',
    target_options={
        'sms': {
            'from_number': '+15017122661',
            'phone_numbers': ['+14155238886']
        }
    }
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("Your order #12345 has been shipped! Track it here: https://example.com/track/12345"),
    Targets:     relaygo.F([]string{"sms"}),
    ScheduledAt: relaygo.F("now"),
    TargetOptions: relaygo.F(map[string]interface{}{
        "sms": map[string]interface{}{
            "from_number":   "+15017122661",
            "phone_numbers": []string{"+14155238886"},
        },
    }),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Your order #12345 has been shipped! Track it here: https://example.com/track/12345")
    .addTarget("sms")
    .scheduledAt("now")
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("sms", JsonValue.from(Map.of(
            "from_number", "+15017122661",
            "phone_numbers", List.of("+14155238886")
        )))
        .build())
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Your order #12345 has been shipped! Track it here: https://example.com/track/12345",
    "targets": ["sms"],
    "scheduled_at": "now",
    "target_options": {
      "sms": {
        "from_number": "+15017122661",
        "phone_numbers": ["+14155238886"]
      }
    }
  }'

Media Message (MMS)

Send images or videos via MMS. Up to 10 media URLs per message.

const post = await client.posts.create({
  content: "Here's your receipt!",
  targets: ['sms'],
  media: [
    { url: 'https://cdn.example.com/receipt.png', type: 'image' }
  ],
  scheduled_at: 'now',
  target_options: {
    sms: {
      from_number: '+15017122661',
      phone_numbers: ['+14155238886']
    }
  }
});
post = client.posts.create(
    content="Here's your receipt!",
    targets=['sms'],
    media=[
        {'url': 'https://cdn.example.com/receipt.png', 'type': 'image'}
    ],
    scheduled_at='now',
    target_options={
        'sms': {
            'from_number': '+15017122661',
            'phone_numbers': ['+14155238886']
        }
    }
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("Here's your receipt!"),
    Targets:     relaygo.F([]string{"sms"}),
    ScheduledAt: relaygo.F("now"),
    Media: relaygo.F([]relaygo.PostNewParamsMedia{
        {URL: relaygo.F("https://cdn.example.com/receipt.png"), Type: relaygo.F(relaygo.PostNewParamsMediaTypeImage)},
    }),
    TargetOptions: relaygo.F(map[string]interface{}{
        "sms": map[string]interface{}{
            "from_number":   "+15017122661",
            "phone_numbers": []string{"+14155238886"},
        },
    }),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Here's your receipt!")
    .addTarget("sms")
    .scheduledAt("now")
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/receipt.png")
        .type(PostCreateParams.Media.Type.IMAGE)
        .build())
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("sms", JsonValue.from(Map.of(
            "from_number", "+15017122661",
            "phone_numbers", List.of("+14155238886")
        )))
        .build())
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Here'\''s your receipt!",
    "targets": ["sms"],
    "media": [
      {"url": "https://cdn.example.com/receipt.png", "type": "image"}
    ],
    "scheduled_at": "now",
    "target_options": {
      "sms": {
        "from_number": "+15017122661",
        "phone_numbers": ["+14155238886"]
      }
    }
  }'

Media-Only Message (MMS)

Send media without any text body.

const post = await client.posts.create({
  targets: ['sms'],
  media: [
    { url: 'https://cdn.example.com/promo.jpg', type: 'image' }
  ],
  scheduled_at: 'now',
  target_options: {
    sms: {
      from_number: '+15017122661',
      phone_numbers: ['+14155238886']
    }
  }
});
post = client.posts.create(
    targets=['sms'],
    media=[
        {'url': 'https://cdn.example.com/promo.jpg', 'type': 'image'}
    ],
    scheduled_at='now',
    target_options={
        'sms': {
            'from_number': '+15017122661',
            'phone_numbers': ['+14155238886']
        }
    }
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Targets:     relaygo.F([]string{"sms"}),
    ScheduledAt: relaygo.F("now"),
    Media: relaygo.F([]relaygo.PostNewParamsMedia{
        {URL: relaygo.F("https://cdn.example.com/promo.jpg"), Type: relaygo.F(relaygo.PostNewParamsMediaTypeImage)},
    }),
    TargetOptions: relaygo.F(map[string]interface{}{
        "sms": map[string]interface{}{
            "from_number":   "+15017122661",
            "phone_numbers": []string{"+14155238886"},
        },
    }),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .addTarget("sms")
    .scheduledAt("now")
    .addMedia(PostCreateParams.Media.builder()
        .url("https://cdn.example.com/promo.jpg")
        .type(PostCreateParams.Media.Type.IMAGE)
        .build())
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("sms", JsonValue.from(Map.of(
            "from_number", "+15017122661",
            "phone_numbers", List.of("+14155238886")
        )))
        .build())
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "targets": ["sms"],
    "media": [
      {"url": "https://cdn.example.com/promo.jpg", "type": "image"}
    ],
    "scheduled_at": "now",
    "target_options": {
      "sms": {
        "from_number": "+15017122661",
        "phone_numbers": ["+14155238886"]
      }
    }
  }'

Multi-Recipient

Send the same message to multiple phone numbers. Each recipient receives an individual message.

const post = await client.posts.create({
  content: 'Reminder: Team meeting at 3 PM today.',
  targets: ['sms'],
  scheduled_at: 'now',
  target_options: {
    sms: {
      from_number: '+15017122661',
      phone_numbers: [
        '+14155238886',
        '+14155238887',
        '+14155238888'
      ]
    }
  }
});
post = client.posts.create(
    content='Reminder: Team meeting at 3 PM today.',
    targets=['sms'],
    scheduled_at='now',
    target_options={
        'sms': {
            'from_number': '+15017122661',
            'phone_numbers': [
                '+14155238886',
                '+14155238887',
                '+14155238888'
            ]
        }
    }
)
post, err := client.Posts.New(context.TODO(), relaygo.PostNewParams{
    Content:     relaygo.F("Reminder: Team meeting at 3 PM today."),
    Targets:     relaygo.F([]string{"sms"}),
    ScheduledAt: relaygo.F("now"),
    TargetOptions: relaygo.F(map[string]interface{}{
        "sms": map[string]interface{}{
            "from_number": "+15017122661",
            "phone_numbers": []string{
                "+14155238886",
                "+14155238887",
                "+14155238888",
            },
        },
    }),
})
PostCreateResponse post = client.posts().create(PostCreateParams.builder()
    .content("Reminder: Team meeting at 3 PM today.")
    .addTarget("sms")
    .scheduledAt("now")
    .targetOptions(PostCreateParams.TargetOptions.builder()
        .putAdditionalProperty("sms", JsonValue.from(Map.of(
            "from_number", "+15017122661",
            "phone_numbers", List.of("+14155238886", "+14155238887", "+14155238888")
        )))
        .build())
    .build());
curl -X POST https://api.relayapi.dev/v1/posts \
  -H "Authorization: Bearer $RELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Reminder: Team meeting at 3 PM today.",
    "targets": ["sms"],
    "scheduled_at": "now",
    "target_options": {
      "sms": {
        "from_number": "+15017122661",
        "phone_numbers": [
          "+14155238886",
          "+14155238887",
          "+14155238888"
        ]
      }
    }
  }'

Media Requirements

Images

PropertyRequirement
Max per message10 (MMS)
FormatsJPEG, PNG, GIF
Max file size5 MB

Videos

PropertyRequirement
Max per message1
FormatsMP4
Max file size5 MB

target_options Fields

All fields go inside target_options.sms on your post request.

FieldTypeDefaultDescription
contentstringOverride message content for SMS specifically
mediaobject[]Override media for SMS specifically
from_numberstringRequired. Your Twilio phone number in E.164 format (e.g., "+15017122661")
phone_numbersstring[]Required. Recipient phone numbers in E.164 format

Common Errors

ErrorCauseFix
Missing from_numberNo sender phone number providedAdd from_number in target_options.sms with your Twilio phone number.
Missing phone_numbersNo recipients providedAdd at least one phone number to phone_numbers array.
Empty contentNo body text or media providedAdd text content or at least one media URL.
Invalid phone numberPhone number is not in E.164 formatUse E.164 format with country code (e.g., +14155238886).
Twilio auth errorInvalid Account SID or Auth TokenVerify your Twilio credentials in the connected account settings.

Known Quirks

  • Twilio-powered — you must bring your own Twilio Account SID, Auth Token, and phone number.
  • Auto-segmentation — messages over 160 characters are split into multiple segments by Twilio (billed per segment).
  • 1,600-character limit — Twilio allows up to 1,600 characters per message (auto-segmented).
  • MMS availability — MMS (media messages) is only available in the US and Canada. International messages are SMS-only.
  • Multi-recipient sends individually — each phone number receives a separate message (not a group text).
  • Media URLs must be publicly accessible — Twilio fetches media from the provided URLs.
  • Partial failures are possible — when sending to multiple recipients, some may succeed while others fail.

Automations

SMS automation is provider-abstracted — Twilio and Telnyx both supported. Provider is stored on the account metadata as provider (defaults to twilio).

Triggers

TypeFires on
sms_receivedInbound SMS (any provider)

Send nodes

NodeTwilio endpointTelnyx endpointRequired fields
sms_sendPOST /2010-04-01/Accounts/{sid}/Messages.jsonPOST /v2/messagestext
sms_send_mmssame (+ MediaUrl)same (+ media_urls[])media_url, optional text

Auth is Basic (Twilio: sid:auth_token) or Bearer (Telnyx: API key), chosen from the account's provider field.

Found something wrong? Help us improve this page.

On this page

Submit an Issue
Requires a GitHub account.View repo