Send Thread Messages

Last updated: 2026-06-02Contributors
Edit this page

Any SMS handler of a common resource can initiate a new message to a customer using a shared phone number associated with the resource. However, if an open thread already exists for that pair of phone numbers, only the thread’s current assignee can send reply messages. This ensures consistent ownership and prevents conflicting responses within an active conversation.

Before learning how to send thread messages using the /restapi/v1.0/account/~/message-threads/messages endpoint, developers should first understand how to identify shared phone numbers and the SMS handlers assigned to common resources.

To send thread messages, your application must:

  • Authenticate the user extension who is one of the SMS handlers.
  • Retrieve the list of phone numbers assigned to the authenticated user by calling the /restapi/v1.0/account/~/extension/~/phone-number endpoint and select a shared phone number from the API's response. Remember to detect if the "SmsSender" (and "MmsSender") feature is attached to the selected shared phone number.
Sample data

Example info of a main company phone number assigned to a SMS handler.

{
  "features": [
    "CallerId",
    "SmsSender", // Indicates that the number has the SMS feature
    "MmsSender" // Indicates that the number has the MMS feature
  ],
  "id": 656379052,
  "phoneNumber": "+1408412XXXX",
  "usageType": "MainCompanyNumber", // This is the account main company number
  "status": "Normal",
  "country": {
    ...
  },
  "primary": false,
  "callerIdName": "..."
}

Example info of a company phone number assigned to a SMS hander.

{
  "features": [
    "CallerId",
    "SmsSender", // Indicates that the number has the SMS feature
    "MmsSender" // Indicates that the number has the MMS feature
  ],
  "id": 656614052,
  "phoneNumber": "+1619837XXXX",
  "usageType": "CompanyNumber", // This is a company number which normally assigned to the auto-receptionist
  "status": "Normal",
  "country": {
    ...
  },
  "primary": false,
  "callerIdName": "..."
}

Example info of a call queue direct phone number assigned to a SMS hander.

{
  "features": [
    "CallerId",
    "SmsSender", // Indicates that the number has the SMS feature
    "MmsSender" // Indicates that the number has the MMS feature
  ],
  "id": 121366771234,
  "phoneNumber": "+1555987XXXX", // The phone number
  "usageType": "DirectNumber",
 // Indicates that the phone number above belongs to the common resource below
  "extension": {
    "uri": "...",
    "id": 62284871234, // The Id of the common resource
    "extensionNumber": "11601",
    "name": "My Call Queue",
    // Call queue extension
    "type": "Department"
  },
  ...
}

Example info of an IVR direct phone number assigned to a SMS hander.

{
  "features": [
    "CallerId",
    "SmsSender", // Indicates that the number has the SMS feature
    "MmsSender" // Indicates that the number has the MMS feature
  ],
  "id": 657014052,
  "phoneNumber": "+1657276XXXX",
  "usageType": "DirectNumber",
  // Indicates that the phone number above belongs to the common resource below
  "extension": {
    "uri": "...",
    "id": 497134052, // The Id of the common resource
    "extensionNumber": "1002",
    "name": "IVR Menu 1002",
    // IVR menu extension
    "type": "IvrMenu"
  },
  ...
}

Example info of a site's direct phone number assigned to a SMS hander.

{
  "features": [
    "CallerId",
    "SmsSender", // Indicates that the number has the SMS feature
    "MmsSender" // Indicates that the number has the MMS feature
  ],
  "id": 657031052,
  "phoneNumber": "+1657505XXXX",
  "usageType": "DirectNumber",
  // Indicates that the phone number above belongs to the common resource below
  "extension": {
    "uri": "...",
    "id": 497128052, // The Id of the common resource
    "extensionNumber": "30001",
    "name": "Site 01",
    // Site extension
    "type": "Site"
  },
  ...
}
  • Use the selected shared phone number to send a message.

Use cases and sample codes

Running the code

The sample code below is written using the official RingCentral SDKs. Before running the examples, make sure to install the appropriate SDK for your programming language.

If you have tried the SMS quick start, you can just copy all the functions in the example and add them to the quick start project then call the read_extension_phone_number_detect_sms_feature() function. Otherwise, edit the following variables with your app and user credentials before running the code.

  • RC_APP_CLIENT_ID
  • RC_APP_CLIENT_SECRET
  • RC_USER_JWT

Also, make sure to use valid recipient phone numbers in the sample bodyParams

Use Case

A financial advising team wants to maintain direct communication with clients through SMS. There are 4 advisors in the team and each advisor manages a specific group of clients based on category or service tier. All advisors are required to use the same call queue direct number (+1-555-987-XXXX) to send appointment reminders, respond to inquiries, and share updates on financial plans. The call queue's name is "Financial Advising Queue".

Sample code

The sample code below detects the call queue’s direct phone number, checks whether the number has the SmsSender feature, and sends a message.

const RC = require('@ringcentral/sdk').SDK

// Instantiate the SDK and get the platform instance
var rcsdk = new RC({
    server: "https://platform.ringcentral.com",
    clientId: "RC_APP_CLIENT_ID",
    clientSecret: "RC_APP_CLIENT_SECRET"
});
var platform = rcsdk.platform();

/* Authenticate a user using a personal JWT token */
platform.login({ jwt: "RC_USER_JWT" })

platform.on(platform.events.loginSuccess, function(e){
    read_shared_phone_number_detect_sms_feature()
});

platform.on(platform.events.loginError, function(e){
    console.log("Unable to authenticate to platform. Check credentials.", e.message)
    process.exit(1)
});

/*
  Read the shared phone number that currently assigned to the authenticated user and detect if a phone number
  has the SMS capability
*/
async function read_shared_phone_number_detect_sms_feature(){
  try {
      let endpoint = "/restapi/v1.0/account/~/extension/~/phone-number"
      var resp = await platform.get(endpoint)
      var jsonObj = await resp.json()
      for (var record of jsonObj.records){
        // Find the "Financial Advising Queue" call queue's direct phone number
        if (record.hasOwnProperty('extension') && record.extension.name == "Financial Advising Queue"){
          for (feature of record.features){
              if (feature == "SmsSender"){
                await send_thread_message(record.phoneNumber)
                return
              }
          }
        }
      }
      if (jsonObj.records.length == 0)
        console.log("This user does not own a phone number!")
      else
        console.log("None of this user's phone number(s) has SMS capability!")
  } catch(e) {
      console.log(e.message)
  }
}

/*
 Send a thread message to a recipient phone number
*/
async function send_thread_message(fromNumber) {
  try{
    let bodyParams = {
      from: { phoneNumber: fromNumber },
      to: [{ phoneNumber: "Recipient-1-Phone-Number" }],
      text: "Hi Tom ...",

    }
    let endpoint = "/restapi/v1.0/account/~/message-threads/messages"
    var resp = await platform.post(endpoint, bodyParams)
    var jsonObj = await resp.json()
    console.log("Resp: ", JSON.stringify(jsonObj, null, 4))
  }catch(e){
    console.log(e.message)
  }
}
import json
from ringcentral import SDK

# Read the shared phone number that currently assigned to the authenticated user and detect if a phone number
# has the SMS capability
def read_shared_phone_number_detect_sms_feature():
    try:
        endpoint = "/restapi/v1.0/account/~/extension/~/phone-number"
        resp = platform.get(endpoint)
        jsonObj = resp.json_dict()
        for record in jsonObj['records']:
            # Find the "Financial Advising Queue" call queue's direct phone number
            if 'extension' in record and record['extension']['name'] == "Financial Advising Queue":
                for feature in record['features']:
                    if feature == "SmsSender":
                        send_thread_message(record.phoneNumber)
                        return

        if len(jsonObj['records']) == 0:
            print ("This user does not own a phone number!")
        else:
            print ("None of this user's phone number(s) has the SMS capability!")
    except Exception as e:
        print (e)

#
# Send a thread message to a recipient phone number
#
def send_thread_message(fromNumber):
    try:
        bodyParams = {
              "from": { "phoneNumber": fromNumber },
              "to": [ { "phoneNumber": "Recipient-1-Phone-Number" } ],
              "text": "Hi Tom ...",
            }
        endpoint = "/restapi/v1.0/account/~/message-threads/messages"
        resp = platform.post(endpoint, bodyParams)
        jsonObj = resp.json_dict()
        print(json.dumps(jsonObj, indent=2, sort_keys=True))
    except Exception as e:
        print (e)

# Authenticate a user using a personal JWT token
def login():
    try:
      platform.login( jwt= "RC_USER_JWT" )
      read_shared_phone_number_detect_sms_feature()
    except Exception as e:
      print ("Unable to authenticate to platform. Check credentials." + str(e))

# Instantiate the SDK and get the platform instance
rcsdk = SDK("RC_APP_CLIENT_ID", "RC_APP_CLIENT_SECRET", "https://platform.ringcentral.com")
platform = rcsdk.platform()

login()
<?php
require('vendor/autoload.php');

// Instantiate the SDK and get the platform instance
$rcsdk = new RingCentral\SDK\SDK( "RC_APP_CLIENT_ID", "RC_APP_CLIENT_SECRET", "https://platform.ringcentral.com" );
$platform = $rcsdk->platform();

/* Authenticate a user using a personal JWT token */
try {
  $platform->login(["jwt" => "RC_USER_JWT"]);
}catch (\RingCentral\SDK\Http\ApiException $e) {
  exit("Unable to authenticate to platform. Check credentials. " . $e->message . PHP_EOL);
}
read_shared_phone_number_detect_sms_feature();

/*
  Read the shared phone number that currently assigned to the authenticated user and detect if a phone number
  has the SMS capability
*/
function read_shared_phone_number_detect_sms_feature(){
  global $platform;
  $endpoint = "/restapi/v1.0/account/~/extension/~/phone-number";
  $resp = $platform->get($endpoint);
  $jsonObj = $resp->json();
  foreach ($resp->json()->records as $record){
    // Find the "Financial Advising Queue" call queue's direct phone number
    if (isset($record['extension']) && $record['extension']['name'] === "Financial Advising Queue"){
      foreach ($record->features as $feature){
        if ($feature == "SmsSender"){
          send_thread_message($record->phoneNumber);
          return;
        }
      }
    }
  }
  if (count($jsonObj->records) == 0)
    exit("This user does not own a phone number!");
  else
    exit("None of this user's phone number(s) has the SMS capability!");
}

/*
 Send a thread message to a recipient phone number
*/
function send_thread_message($fromNumber) {
  global $platform, $RECIPIENT;
  try {
    $bodyParams = [
        "from" => [ "phoneNumber" => $fromNumber ],
        "to" => [ ["phoneNumber" => "Recipient-1"] ],
        "text" => "Hi Tom ...",
        ];

    $endpoint = "/restapi/v1.0/account/~/message-threads/messages";
    $resp = $platform->post($endpoint, $bodyParams);
    $jsonObj = $resp->json();
    print_r (json_encode($jsonObj, JSON_PRETTY_PRINT) . PHP_EOL);
  } catch (\RingCentral\SDK\Http\ApiException $e) {
    exit("Message: " . $e->message . PHP_EOL);
  }
}
?>
require 'ringcentral'

# Read the shared phone number that currently assigned to the authenticated user and detect if a phone number
# has the SMS capability
def read_shared_phone_number_detect_sms_feature()
  begin
    endpoint = "/restapi/v1.0/account/~/extension/~/phone-number"
    resp = $platform.get(endpoint)
    for record in resp.body['records'] do
      # Find the "Financial Advising Queue" call queue's direct phone number
      if record.key?('extension') && record['extension']['name'] == "Financial Advising Queue"
        for feature in record['features'] do
          if feature == "SmsSender"
            send_thread_message(record['phoneNumber'])
            return
          end
        end
      end
    end
    if resp.body['records'].length == 0
      puts ("This user does not own a phone number!")
    else
      puts("None of this user's phone number(s) has the SMS capability!")
    end
  rescue StandardError => e
    puts (e)
  end
end

#
# Send a thread message to a recipient phone number
#
def send_thread_message(fromNumber)
  begin
    bodyParams = {
      from: { phoneNumber: fromNumber },
      to: [ { phoneNumber: "Recipient-1-Phone-Number" } ],
      text: "Hi Tom ..."
    }
    endpoint = "/restapi/v1.0/account/~/message-threads/messages"
    resp = $platform.post(endpoint, payload: bodyParams)
    puts(resp.body)
  rescue StandardError => e
    puts (e)
  end
end

# Authenticate a user using a personal JWT token
def login()
  begin
    $platform.authorize( jwt: "RC_USER_JWT" )
    read_shared_phone_number_detect_sms_feature()
  rescue StandardError => e
    puts ("Unable to authenticate to platform. Check credentials." + e.to_s)
  end
end

# Instantiate the SDK and get the platform instance
$platform = RingCentral.new( "RC_APP_CLIENT_ID", "RC_APP_CLIENT_SECRET", "https://platform.ringcentral.com" )

login()

Sample response

The code samples above would all produce a response that would appear similar to the one below.

{
    "id": "2904652647",
    "threadId": "805808d1-31d2-4f01-867b-a5cff52d00ea",
    "recordType": "AliveMessage",
    "lastModifiedTime": "2026-03-12T21:06:33.706Z",
    "availability": "Alive",
    "creationTime": "2026-03-12T21:06:33.706Z",
    "direction": "Outbound",
    "messageStatus": "Queued",
    "text": "Hi Tom ...",
    "author": {
        "extensionId": "6229532XXXX",
        "name": "Wayne T",
        "extensionType": "User"
    }
}

If an open thread already exists for the pair of phone numbers and the new sender is a different SMS handler, the API call will fail with an error similar to the one below:

{
  "errors": [
    {
      "errorCode": "MSG-427",
      "message": "The thread is not assigned to extension [59587XXXX] at the moment.",
      "extensionId": "59587XXXX"
    }
  ]
}