Skip to content

Bulk send

Sends many different messages in a single call — each with its own text, its own sender and its own destinations. When every recipient gets the same body, sms is the operation you want; sending a batch of identical messages through bulk works but wastes the batch budget.

http
POST https://my.textme.co.il/api

bulk Send a batch

The body wraps a messages element holding up to 2,500 sms objects. Each one is a miniature version of the sms payload: sender, destinations, text and optional links. Settings that apply to the batch as a whole — scheduling, recency filtering, campaign name — sit at the top level, outside messages.

Parameters

NameTypeDescriptionRequired
bulkobjectRoot element.✔️
userobjectContains the user element.✔️
usernamestringThe username of the account by which you are recognized in the system.✔️
messagesarray of objectsContains all the sms objects. The number of objects must not exceed 2500.✔️
messages.sms.sourcestringSender for this message. Maximum 11 characters, digits and English letters only, no +. Must be a verified sender.✔️
messages.sms.destinationsobjectDestinations for this message. May hold multiple phone elements.✔️
messages.sms.phonestringA destination number, formatted 5xxxxxxx or 05xxxxxxx.✔️
messages.sms.idstringAttribute of phone. Your own external id for this destination, for later delivery reports.
messages.sms.messagestringThe body for this message. Maximum 1005 characters.✔️
messages.sms.linksobjectLinks to shorten inside this message. [link-id] in the body marks where each appears.
messages.sms.cl_idstringSend this message to a contact list as well.
timingstringSchedule the whole batch, formatted dd/mm/yy hh:mm. Sent immediately when absent.
add_unsubscribeintOpt-out mechanism for the batch: 3 for a link, 2 for reply-to-remove.
temp_blstringSkip destinations messaged in the last n days, 1–14.
includes_internationalint1 to allow non-Israeli destinations, 0 otherwise.
campaign_namestringName for the batch on system reports, and the handle for cancelling by name. Maximum 50 characters.

Request example

Two messages, each to its own destinations, the second carrying a short link:

xml
<?xml version="1.0" encoding="UTF-8"?>
<bulk>
    <user>
        <username>Leeroy</username>
    </user>
    <messages>
        <sms>
            <source>DemoAPI</source>
            <destinations>
                <phone id="external id1">5xxxxxxxx</phone>
                <phone id="external id2">5xxxxxxxx</phone>
                <phone>5xxxxxxxx</phone>
                <phone id="">5xxxxxxxx</phone>
            </destinations>
            <message>This is a sample message</message>
        </sms>
        <sms>
            <source>DemoAPI</source>
            <destinations>
                <phone id="">5xxxxxxxx</phone>
            </destinations>
            <message>This is a different message sent. [link-a1]</message>
            <links>
                <link id="a1">https://www.example.com/path/to/resource</link>
            </links>
        </sms>
    </messages>
    <timing>10/10/17 10:10</timing>
    <temp_bl>2</temp_bl>
    <includes_international>1</includes_international>
    <campaign_name>Sample Bulk Campaign</campaign_name>
</bulk>
json
{
  "bulk": {
    "user": {
      "username": "Leeroy"
    },
    "messages": {
      "sms": [
        {
          "source": "DemoAPI",
          "destinations": {
            "phone": [
              {
                "$": {
                  "id": "external id1"
                },
                "_": "5xxxxxxxx"
              },
              {
                "$": {
                  "id": "external id2"
                },
                "_": "5xxxxxxxx"
              },
              {
                "_": "5xxxxxxxx"
              },
              {
                "$": {
                  "id": ""
                },
                "_": "5xxxxxxxx"
              }
            ]
          },
          "message": "This is a sample message"
        },
        {
          "source": "DemoAPI",
          "destinations": {
            "phone": {
              "$": {
                "id": ""
              },
              "_": "5xxxxxxxx"
            }
          },
          "message": "This is a different message sent. [link-a1]",
          "links": {
            "link": [
              {
                "$": {
                  "id": "a1"
                },
                "_": "https://www.example.com/path/to/resource"
              }
            ]
          }
        }
      ]
    },
    "timing": "10/10/17 10:10",
    "temp_bl": "2",
    "includes_international": "1",
    "campaign_name": "Sample Bulk Campaign"
  }
}
bash
curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
  "bulk": {
    "user": {
      "username": "Leeroy"
    },
    "messages": {
      "sms": [
        {
          "source": "DemoAPI",
          "destinations": {
            "phone": [
              {
                "$": {
                  "id": "external id1"
                },
                "_": "5xxxxxxxx"
              },
              {
                "$": {
                  "id": "external id2"
                },
                "_": "5xxxxxxxx"
              },
              {
                "_": "5xxxxxxxx"
              },
              {
                "$": {
                  "id": ""
                },
                "_": "5xxxxxxxx"
              }
            ]
          },
          "message": "This is a sample message"
        },
        {
          "source": "DemoAPI",
          "destinations": {
            "phone": {
              "$": {
                "id": ""
              },
              "_": "5xxxxxxxx"
            }
          },
          "message": "This is a different message sent. [link-a1]",
          "links": {
            "link": [
              {
                "$": {
                  "id": "a1"
                },
                "_": "https://www.example.com/path/to/resource"
              }
            ]
          }
        }
      ]
    },
    "timing": "10/10/17 10:10",
    "temp_bl": "2",
    "includes_international": "1",
    "campaign_name": "Sample Bulk Campaign"
  }
}'
js
// Node.js 18+ / browsers — no dependencies
const response = await fetch('https://my.textme.co.il/api', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.TEXTME_API_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    bulk: {
      user: {
        username: 'Leeroy',
      },
      messages: {
        sms: [
          {
            source: 'DemoAPI',
            destinations: {
              phone: [
                {
                  $: {
                    id: 'external id1',
                  },
                  _: '5xxxxxxxx',
                },
                {
                  $: {
                    id: 'external id2',
                  },
                  _: '5xxxxxxxx',
                },
                {
                  _: '5xxxxxxxx',
                },
                {
                  $: {
                    id: '',
                  },
                  _: '5xxxxxxxx',
                },
              ],
            },
            message: 'This is a sample message',
          },
          {
            source: 'DemoAPI',
            destinations: {
              phone: {
                $: {
                  id: '',
                },
                _: '5xxxxxxxx',
              },
            },
            message: 'This is a different message sent. [link-a1]',
            links: {
              link: [
                {
                  $: {
                    id: 'a1',
                  },
                  _: 'https://www.example.com/path/to/resource',
                },
              ],
            },
          },
        ],
      },
      timing: '10/10/17 10:10',
      temp_bl: '2',
      includes_international: '1',
      campaign_name: 'Sample Bulk Campaign',
    },
  }),
})

const result = await response.json()

// Errors arrive as HTTP 200 too — the payload status is what counts
if (Number(result.status) !== 0) {
  throw new Error(`TextMe ${result.status}: ${result.message}`)
}

console.log(result)
php
<?php
// composer require guzzlehttp/guzzle

$client = new \GuzzleHttp\Client([
    'headers' => [
        'Authorization' => 'Bearer '.getenv('TEXTME_API_TOKEN'),
        'Accept' => 'application/json',
    ],
]);

$response = $client->post('https://my.textme.co.il/api', [
    'json' => [
        'bulk' => [
            'user' => [
                'username' => 'Leeroy',
            ],
            'messages' => [
                'sms' => [
                    [
                        'source' => 'DemoAPI',
                        'destinations' => [
                            'phone' => [
                                [
                                    '$' => [
                                        'id' => 'external id1',
                                    ],
                                    '_' => '5xxxxxxxx',
                                ],
                                [
                                    '$' => [
                                        'id' => 'external id2',
                                    ],
                                    '_' => '5xxxxxxxx',
                                ],
                                [
                                    '_' => '5xxxxxxxx',
                                ],
                                [
                                    '$' => [
                                        'id' => '',
                                    ],
                                    '_' => '5xxxxxxxx',
                                ],
                            ],
                        ],
                        'message' => 'This is a sample message',
                    ],
                    [
                        'source' => 'DemoAPI',
                        'destinations' => [
                            'phone' => [
                                '$' => [
                                    'id' => '',
                                ],
                                '_' => '5xxxxxxxx',
                            ],
                        ],
                        'message' => 'This is a different message sent. [link-a1]',
                        'links' => [
                            'link' => [
                                [
                                    '$' => [
                                        'id' => 'a1',
                                    ],
                                    '_' => 'https://www.example.com/path/to/resource',
                                ],
                            ],
                        ],
                    ],
                ],
            ],
            'timing' => '10/10/17 10:10',
            'temp_bl' => '2',
            'includes_international' => '1',
            'campaign_name' => 'Sample Bulk Campaign',
        ],
    ],
]);

$result = json_decode($response->getBody()->getContents(), true);

// Errors arrive as HTTP 200 too — the payload status is what counts
if ((int) $result['status'] !== 0) {
    throw new RuntimeException("TextMe {$result['status']}: {$result['message']}");
}

print_r($result);
php
<?php

use Illuminate\Support\Facades\Http;

$result = Http::withToken(config('services.textme.token'))
    ->acceptJson()
    ->post('https://my.textme.co.il/api', [
        'bulk' => [
            'user' => [
                'username' => 'Leeroy',
            ],
            'messages' => [
                'sms' => [
                    [
                        'source' => 'DemoAPI',
                        'destinations' => [
                            'phone' => [
                                [
                                    '$' => [
                                        'id' => 'external id1',
                                    ],
                                    '_' => '5xxxxxxxx',
                                ],
                                [
                                    '$' => [
                                        'id' => 'external id2',
                                    ],
                                    '_' => '5xxxxxxxx',
                                ],
                                [
                                    '_' => '5xxxxxxxx',
                                ],
                                [
                                    '$' => [
                                        'id' => '',
                                    ],
                                    '_' => '5xxxxxxxx',
                                ],
                            ],
                        ],
                        'message' => 'This is a sample message',
                    ],
                    [
                        'source' => 'DemoAPI',
                        'destinations' => [
                            'phone' => [
                                '$' => [
                                    'id' => '',
                                ],
                                '_' => '5xxxxxxxx',
                            ],
                        ],
                        'message' => 'This is a different message sent. [link-a1]',
                        'links' => [
                            'link' => [
                                [
                                    '$' => [
                                        'id' => 'a1',
                                    ],
                                    '_' => 'https://www.example.com/path/to/resource',
                                ],
                            ],
                        ],
                    ],
                ],
            ],
            'timing' => '10/10/17 10:10',
            'temp_bl' => '2',
            'includes_international' => '1',
            'campaign_name' => 'Sample Bulk Campaign',
        ],
    ])
    ->throw()
    ->json();

// Errors arrive as HTTP 200 too — the payload status is what counts
throw_if((int) $result['status'] !== 0, RuntimeException::class,
    "TextMe {$result['status']}: {$result['message']}");

logger()->info('TextMe', $result);
python
# pip install httpx
import os

import httpx

response = httpx.post(
    "https://my.textme.co.il/api",
    headers={"Authorization": f"Bearer {os.environ['TEXTME_API_TOKEN']}"},
    json={
        "bulk": {
            "user": {
                "username": "Leeroy",
            },
            "messages": {
                "sms": [
                    {
                        "source": "DemoAPI",
                        "destinations": {
                            "phone": [
                                {
                                    "$": {
                                        "id": "external id1",
                                    },
                                    "_": "5xxxxxxxx",
                                },
                                {
                                    "$": {
                                        "id": "external id2",
                                    },
                                    "_": "5xxxxxxxx",
                                },
                                {
                                    "_": "5xxxxxxxx",
                                },
                                {
                                    "$": {
                                        "id": "",
                                    },
                                    "_": "5xxxxxxxx",
                                },
                            ],
                        },
                        "message": "This is a sample message",
                    },
                    {
                        "source": "DemoAPI",
                        "destinations": {
                            "phone": {
                                "$": {
                                    "id": "",
                                },
                                "_": "5xxxxxxxx",
                            },
                        },
                        "message": "This is a different message sent. [link-a1]",
                        "links": {
                            "link": [
                                {
                                    "$": {
                                        "id": "a1",
                                    },
                                    "_": "https://www.example.com/path/to/resource",
                                },
                            ],
                        },
                    },
                ],
            },
            "timing": "10/10/17 10:10",
            "temp_bl": "2",
            "includes_international": "1",
            "campaign_name": "Sample Bulk Campaign",
        },
    },
)
response.raise_for_status()
result = response.json()

# Errors arrive as HTTP 200 too — the payload status is what counts
if int(result["status"]) != 0:
    raise RuntimeError(f"TextMe {result['status']}: {result['message']}")

print(result)
go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

func main() {
	payload, _ := json.Marshal(map[string]any{
		"bulk": map[string]any{
			"user": map[string]any{
				"username": "Leeroy",
			},
			"messages": map[string]any{
				"sms": []any{
					map[string]any{
						"source": "DemoAPI",
						"destinations": map[string]any{
							"phone": []any{
								map[string]any{
									"$": map[string]any{
										"id": "external id1",
									},
									"_": "5xxxxxxxx",
								},
								map[string]any{
									"$": map[string]any{
										"id": "external id2",
									},
									"_": "5xxxxxxxx",
								},
								map[string]any{
									"_": "5xxxxxxxx",
								},
								map[string]any{
									"$": map[string]any{
										"id": "",
									},
									"_": "5xxxxxxxx",
								},
							},
						},
						"message": "This is a sample message",
					},
					map[string]any{
						"source": "DemoAPI",
						"destinations": map[string]any{
							"phone": map[string]any{
								"$": map[string]any{
									"id": "",
								},
								"_": "5xxxxxxxx",
							},
						},
						"message": "This is a different message sent. [link-a1]",
						"links": map[string]any{
							"link": []any{
								map[string]any{
									"$": map[string]any{
										"id": "a1",
									},
									"_": "https://www.example.com/path/to/resource",
								},
							},
						},
					},
				},
			},
			"timing": "10/10/17 10:10",
			"temp_bl": "2",
			"includes_international": "1",
			"campaign_name": "Sample Bulk Campaign",
		},
	})

	req, _ := http.NewRequest("POST", "https://my.textme.co.il/api", bytes.NewReader(payload))
	req.Header.Set("Authorization", "Bearer "+os.Getenv("TEXTME_API_TOKEN"))
	req.Header.Set("Content-Type", "application/json")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	var result struct {
		Status  json.Number `json:"status"`
		Message string      `json:"message"`
	}
	if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
		panic(err)
	}

	// Errors arrive as HTTP 200 too — the payload status is what counts
	if result.Status.String() != "0" {
		panic(fmt.Sprintf("TextMe %s: %s", result.Status, result.Message))
	}

	fmt.Println(result.Message)
}
java
// Java 17+ — java.net.http, no dependencies (parse with Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class TextMeSendBulk {
    public static void main(String[] args) throws Exception {
        String body = """
            {
              "bulk": {
                "user": {
                  "username": "Leeroy"
                },
                "messages": {
                  "sms": [
                    {
                      "source": "DemoAPI",
                      "destinations": {
                        "phone": [
                          {
                            "$": {
                              "id": "external id1"
                            },
                            "_": "5xxxxxxxx"
                          },
                          {
                            "$": {
                              "id": "external id2"
                            },
                            "_": "5xxxxxxxx"
                          },
                          {
                            "_": "5xxxxxxxx"
                          },
                          {
                            "$": {
                              "id": ""
                            },
                            "_": "5xxxxxxxx"
                          }
                        ]
                      },
                      "message": "This is a sample message"
                    },
                    {
                      "source": "DemoAPI",
                      "destinations": {
                        "phone": {
                          "$": {
                            "id": ""
                          },
                          "_": "5xxxxxxxx"
                        }
                      },
                      "message": "This is a different message sent. [link-a1]",
                      "links": {
                        "link": [
                          {
                            "$": {
                              "id": "a1"
                            },
                            "_": "https://www.example.com/path/to/resource"
                          }
                        ]
                      }
                    }
                  ]
                },
                "timing": "10/10/17 10:10",
                "temp_bl": "2",
                "includes_international": "1",
                "campaign_name": "Sample Bulk Campaign"
              }
            }
            """;

        HttpRequest request = HttpRequest.newBuilder(URI.create("https://my.textme.co.il/api"))
            .header("Authorization", "Bearer " + System.getenv("TEXTME_API_TOKEN"))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body))
            .build();

        HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofString());

        // Errors arrive as HTTP 200 too — the payload status is what counts
        System.out.println(response.body());
    }
}
csharp
// .NET 8+ — System.Net.Http
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

var payload = """
    {
      "bulk": {
        "user": {
          "username": "Leeroy"
        },
        "messages": {
          "sms": [
            {
              "source": "DemoAPI",
              "destinations": {
                "phone": [
                  {
                    "$": {
                      "id": "external id1"
                    },
                    "_": "5xxxxxxxx"
                  },
                  {
                    "$": {
                      "id": "external id2"
                    },
                    "_": "5xxxxxxxx"
                  },
                  {
                    "_": "5xxxxxxxx"
                  },
                  {
                    "$": {
                      "id": ""
                    },
                    "_": "5xxxxxxxx"
                  }
                ]
              },
              "message": "This is a sample message"
            },
            {
              "source": "DemoAPI",
              "destinations": {
                "phone": {
                  "$": {
                    "id": ""
                  },
                  "_": "5xxxxxxxx"
                }
              },
              "message": "This is a different message sent. [link-a1]",
              "links": {
                "link": [
                  {
                    "$": {
                      "id": "a1"
                    },
                    "_": "https://www.example.com/path/to/resource"
                  }
                ]
              }
            }
          ]
        },
        "timing": "10/10/17 10:10",
        "temp_bl": "2",
        "includes_international": "1",
        "campaign_name": "Sample Bulk Campaign"
      }
    }
    """;

using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
    "Bearer", Environment.GetEnvironmentVariable("TEXTME_API_TOKEN"));

var response = await http.PostAsync("https://my.textme.co.il/api",
    new StringContent(payload, Encoding.UTF8, "application/json"));

var result = JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement;
var status = result.GetProperty("status").ToString();

// Errors arrive as HTTP 200 too — the payload status is what counts
if (status != "0")
{
    var message = result.GetProperty("message").ToString();
    throw new Exception($"TextMe {status}: {message}");
}

Console.WriteLine(result);
ruby
require "net/http"
require "json"

uri = URI("https://my.textme.co.il/api")

request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch('TEXTME_API_TOKEN')}"
request["Content-Type"] = "application/json"
request.body = JSON.dump({
  "bulk" => {
    "user" => {
      "username" => "Leeroy",
    },
    "messages" => {
      "sms" => [
        {
          "source" => "DemoAPI",
          "destinations" => {
            "phone" => [
              {
                "$" => {
                  "id" => "external id1",
                },
                "_" => "5xxxxxxxx",
              },
              {
                "$" => {
                  "id" => "external id2",
                },
                "_" => "5xxxxxxxx",
              },
              {
                "_" => "5xxxxxxxx",
              },
              {
                "$" => {
                  "id" => "",
                },
                "_" => "5xxxxxxxx",
              },
            ],
          },
          "message" => "This is a sample message",
        },
        {
          "source" => "DemoAPI",
          "destinations" => {
            "phone" => {
              "$" => {
                "id" => "",
              },
              "_" => "5xxxxxxxx",
            },
          },
          "message" => "This is a different message sent. [link-a1]",
          "links" => {
            "link" => [
              {
                "$" => {
                  "id" => "a1",
                },
                "_" => "https://www.example.com/path/to/resource",
              },
            ],
          },
        },
      ],
    },
    "timing" => "10/10/17 10:10",
    "temp_bl" => "2",
    "includes_international" => "1",
    "campaign_name" => "Sample Bulk Campaign",
  },
})

response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
  http.request(request)
end

result = JSON.parse(response.body)

# Errors arrive as HTTP 200 too — the payload status is what counts
raise "TextMe #{result['status']}: #{result['message']}" unless result["status"].to_i.zero?

pp result
rust
// [dependencies]
// reqwest = { version = "0.12", features = ["json"] }
// tokio = { version = "1", features = ["full"] }
// serde_json = "1"
use serde_json::{json, Value};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let result: Value = reqwest::Client::new()
        .post("https://my.textme.co.il/api")
        .bearer_auth(std::env::var("TEXTME_API_TOKEN")?)
        .json(&json!({
          "bulk": {
            "user": {
              "username": "Leeroy"
            },
            "messages": {
              "sms": [
                {
                  "source": "DemoAPI",
                  "destinations": {
                    "phone": [
                      {
                        "$": {
                          "id": "external id1"
                        },
                        "_": "5xxxxxxxx"
                      },
                      {
                        "$": {
                          "id": "external id2"
                        },
                        "_": "5xxxxxxxx"
                      },
                      {
                        "_": "5xxxxxxxx"
                      },
                      {
                        "$": {
                          "id": ""
                        },
                        "_": "5xxxxxxxx"
                      }
                    ]
                  },
                  "message": "This is a sample message"
                },
                {
                  "source": "DemoAPI",
                  "destinations": {
                    "phone": {
                      "$": {
                        "id": ""
                      },
                      "_": "5xxxxxxxx"
                    }
                  },
                  "message": "This is a different message sent. [link-a1]",
                  "links": {
                    "link": [
                      {
                        "$": {
                          "id": "a1"
                        },
                        "_": "https://www.example.com/path/to/resource"
                      }
                    ]
                  }
                }
              ]
            },
            "timing": "10/10/17 10:10",
            "temp_bl": "2",
            "includes_international": "1",
            "campaign_name": "Sample Bulk Campaign"
          }
        }))
        .send()
        .await?
        .json()
        .await?;

    // Errors arrive as HTTP 200 too — the payload status is what counts
    if result["status"] != 0 {
        return Err(format!("TextMe {}: {}", result["status"], result["message"]).into());
    }

    println!("{result}");
    Ok(())
}

Response

xml
<?xml version="1.0" encoding="UTF-8"?>
<sms>
    <status>0</status>
    <message>SMS bulk will be sent</message>
    <shipment_id>xxxxxxx</shipment_id>
</sms>
json
{
  "status": 0,
  "message": "SMS bulk will be sent",
  "shipment_id": "XXXXXXX"
}
FieldTypeDescription
statusint0 — the batch was accepted.
messagestringSMS bulk will be sent.
shipment_idstringIdentifies the campaign created for the whole batch.

One id for the whole batch

shipment_id names the batch, not the individual messages inside it. To reconcile per destination, set an id attribute on every phone and pull delivery reports by external id.

Errors

StatusWhen
2A required element is missing; message names it.
4Not enough credit for the batch.
5Outside the account's permitted sending hours.
8Every destination in the batch is blocklisted.
9A destination is too short or too long.
515A source is not a verified sender.
714temp_bl is outside 1–14.
715Every destination was filtered out by temp_bl.
989A message body is empty or over 1005 characters, or campaign_name is over 50.

Field notes

Batch limits

The hard ceiling is 2,500 sms objects per call — objects, not recipients. A single object addressing 400 numbers counts as one. Splitting a large run into several calls is fine; each returns its own shipment_id, and giving them all the same campaign_name lets you cancel the whole run with one request.

Per-message vs batch-level settings

This split trips people up:

SettingWhere it lives
source, message, destinations, linksInside each sms object
timing, temp_bl, includes_international, campaign_name, add_unsubscribeOn the bulk root

You cannot schedule individual messages within a batch at different times. Different send times mean different calls.

The SOAP interface behaves differently here

sendBulkSms over SOAP ignores timing entirely. The bulk operation documented on this page honours it at the batch level.

Personalisation

bulk personalises by construction: you build the exact text for each recipient. That is the right tool when the variable part comes from your own database — an order number, a delivery window, a one-time code.

When the variable part already lives in TextMe, sms with add_dynamic is less work: store the merge values as dynamic fields on a contact list and send one templated message. See Bulk & personalisation for how to choose.