עבודה עם רשימות תפוצה
רשימת תפוצה היא קהל שנשמר בצד של TextMe. כל חבר נושא עד שישה שדות דינמיים — שם פרטי, עיר, סניף — שהודעה אחת עם תבנית יכולה למזג לכל נמען בנפרד.
המדריך הזה בונה רשימה, מבצע פרסונליזציה לשליחה ממנה, מתחזק אותה, ומכסה את חלקי המודל שמפתיעים.
צעד 1 — יצירת הרשימה
newCL יוצרת רשימה אחת או יותר ויכולה לאכלס אותן באותה קריאה. שדות דינמיים נכנסים על כל איש קשר כ-df1 עד df6:
<?xml version="1.0" encoding="UTF-8"?>
<newCL>
<user>
<username>xxxxxx</username>
</user>
<cl>
<name>name1</name>
<destinations>
<destination>
<phone>055XXXXXXX</phone>
<df1>Israel</df1>
<df2>Israeli</df2>
<df3>Haifa</df3>
</destination>
<destination>
<phone>55XXXXXXX</phone>
</destination>
</destinations>
</cl>
<cl>
<name>name2</name>
<destinations>
<destination>
<phone>055XXXXXXX</phone>
</destination>
</destinations>
</cl>
</newCL>{
"newCL": {
"user": {
"username": "xxxxxx"
},
"cl": [
{
"name": "name1",
"destinations": {
"destination": [
{
"phone": "055XXXXXXX",
"df1": "Israel",
"df2": "Israeli",
"df3": "Haifa"
},
{
"phone": "55XXXXXXX"
}
]
}
},
{
"name": "name2",
"destinations": {
"destination": [
{
"phone": "055XXXXXXX"
}
]
}
}
]
}
}curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"newCL": {
"user": {
"username": "xxxxxx"
},
"cl": [
{
"name": "name1",
"destinations": {
"destination": [
{
"phone": "055XXXXXXX",
"df1": "Israel",
"df2": "Israeli",
"df3": "Haifa"
},
{
"phone": "55XXXXXXX"
}
]
}
},
{
"name": "name2",
"destinations": {
"destination": [
{
"phone": "055XXXXXXX"
}
]
}
}
]
}
}'// Node.js 18+ / דפדפנים — ללא תלויות
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({
newCL: {
user: {
username: 'xxxxxx',
},
cl: [
{
name: 'name1',
destinations: {
destination: [
{
phone: '055XXXXXXX',
df1: 'Israel',
df2: 'Israeli',
df3: 'Haifa',
},
{
phone: '55XXXXXXX',
},
],
},
},
{
name: 'name2',
destinations: {
destination: [
{
phone: '055XXXXXXX',
},
],
},
},
],
},
}),
})
const result = await response.json()
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if (Number(result.status) !== 0) {
throw new Error(`TextMe ${result.status}: ${result.message}`)
}
console.log(result)<?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' => [
'newCL' => [
'user' => [
'username' => 'xxxxxx',
],
'cl' => [
[
'name' => 'name1',
'destinations' => [
'destination' => [
[
'phone' => '055XXXXXXX',
'df1' => 'Israel',
'df2' => 'Israeli',
'df3' => 'Haifa',
],
[
'phone' => '55XXXXXXX',
],
],
],
],
[
'name' => 'name2',
'destinations' => [
'destination' => [
[
'phone' => '055XXXXXXX',
],
],
],
],
],
],
],
]);
$result = json_decode($response->getBody()->getContents(), true);
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if ((int) $result['status'] !== 0) {
throw new RuntimeException("TextMe {$result['status']}: {$result['message']}");
}
print_r($result);<?php
use Illuminate\Support\Facades\Http;
$result = Http::withToken(config('services.textme.token'))
->acceptJson()
->post('https://my.textme.co.il/api', [
'newCL' => [
'user' => [
'username' => 'xxxxxx',
],
'cl' => [
[
'name' => 'name1',
'destinations' => [
'destination' => [
[
'phone' => '055XXXXXXX',
'df1' => 'Israel',
'df2' => 'Israeli',
'df3' => 'Haifa',
],
[
'phone' => '55XXXXXXX',
],
],
],
],
[
'name' => 'name2',
'destinations' => [
'destination' => [
[
'phone' => '055XXXXXXX',
],
],
],
],
],
],
])
->throw()
->json();
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
throw_if((int) $result['status'] !== 0, RuntimeException::class,
"TextMe {$result['status']}: {$result['message']}");
logger()->info('TextMe', $result);# 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={
"newCL": {
"user": {
"username": "xxxxxx",
},
"cl": [
{
"name": "name1",
"destinations": {
"destination": [
{
"phone": "055XXXXXXX",
"df1": "Israel",
"df2": "Israeli",
"df3": "Haifa",
},
{
"phone": "55XXXXXXX",
},
],
},
},
{
"name": "name2",
"destinations": {
"destination": [
{
"phone": "055XXXXXXX",
},
],
},
},
],
},
},
)
response.raise_for_status()
result = response.json()
# גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if int(result["status"]) != 0:
raise RuntimeError(f"TextMe {result['status']}: {result['message']}")
print(result)package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
payload, _ := json.Marshal(map[string]any{
"newCL": map[string]any{
"user": map[string]any{
"username": "xxxxxx",
},
"cl": []any{
map[string]any{
"name": "name1",
"destinations": map[string]any{
"destination": []any{
map[string]any{
"phone": "055XXXXXXX",
"df1": "Israel",
"df2": "Israeli",
"df3": "Haifa",
},
map[string]any{
"phone": "55XXXXXXX",
},
},
},
},
map[string]any{
"name": "name2",
"destinations": map[string]any{
"destination": []any{
map[string]any{
"phone": "055XXXXXXX",
},
},
},
},
},
},
})
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)
}
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if result.Status.String() != "0" {
panic(fmt.Sprintf("TextMe %s: %s", result.Status, result.Message))
}
fmt.Println(result.Message)
}// Java 17+ — java.net.http, ללא תלויות (פענוח עם Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class TextMeClCreate {
public static void main(String[] args) throws Exception {
String body = """
{
"newCL": {
"user": {
"username": "xxxxxx"
},
"cl": [
{
"name": "name1",
"destinations": {
"destination": [
{
"phone": "055XXXXXXX",
"df1": "Israel",
"df2": "Israeli",
"df3": "Haifa"
},
{
"phone": "55XXXXXXX"
}
]
}
},
{
"name": "name2",
"destinations": {
"destination": [
{
"phone": "055XXXXXXX"
}
]
}
}
]
}
}
""";
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());
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
System.out.println(response.body());
}
}// .NET 8+ — System.Net.Http
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var payload = """
{
"newCL": {
"user": {
"username": "xxxxxx"
},
"cl": [
{
"name": "name1",
"destinations": {
"destination": [
{
"phone": "055XXXXXXX",
"df1": "Israel",
"df2": "Israeli",
"df3": "Haifa"
},
{
"phone": "55XXXXXXX"
}
]
}
},
{
"name": "name2",
"destinations": {
"destination": [
{
"phone": "055XXXXXXX"
}
]
}
}
]
}
}
""";
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();
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if (status != "0")
{
var message = result.GetProperty("message").ToString();
throw new Exception($"TextMe {status}: {message}");
}
Console.WriteLine(result);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({
"newCL" => {
"user" => {
"username" => "xxxxxx",
},
"cl" => [
{
"name" => "name1",
"destinations" => {
"destination" => [
{
"phone" => "055XXXXXXX",
"df1" => "Israel",
"df2" => "Israeli",
"df3" => "Haifa",
},
{
"phone" => "55XXXXXXX",
},
],
},
},
{
"name" => "name2",
"destinations" => {
"destination" => [
{
"phone" => "055XXXXXXX",
},
],
},
},
],
},
})
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
result = JSON.parse(response.body)
# גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
raise "TextMe #{result['status']}: #{result['message']}" unless result["status"].to_i.zero?
pp result// [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!({
"newCL": {
"user": {
"username": "xxxxxx"
},
"cl": [
{
"name": "name1",
"destinations": {
"destination": [
{
"phone": "055XXXXXXX",
"df1": "Israel",
"df2": "Israeli",
"df3": "Haifa"
},
{
"phone": "55XXXXXXX"
}
]
}
},
{
"name": "name2",
"destinations": {
"destination": [
{
"phone": "055XXXXXXX"
}
]
}
}
]
}
}))
.send()
.await?
.json()
.await?;
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if result["status"] != 0 {
return Err(format!("TextMe {}: {}", result["status"], result["message"]).into());
}
println!("{result}");
Ok(())
}<?xml version="1.0" encoding="UTF-8"?>
<newCL>
<status>0</status>
<message>conact list successfully created</message>
<errors>
<error>The phone is too long or too short or contain characters and therefore not added</error>
</errors>
<identifiers>
<identifier>17419</identifier>
</identifiers>
</newCL>{
"status": 0,
"message": "conact list successfully created",
"errors": [
"The phone is too long or too short or contain characters and therefore not added"
],
"identifiers": "17419"
}שני דברים שצריך לקרוא בתשובה הזו
identifiers מחזיק את המזהה של הרשימה החדשה — ה-cl_id שאליו שולחים. הוא לא מופיע בשום מקום אחר; שמרו אותו עכשיו או חפשו אותו בהמשך עם getCL.
errors מחזיק את השורות שנדחו — בדרך כלל מספר פגום. הקריאה עדיין מחזירה status: 0, כי הרשימה כן נוצרה. התייחסות ל-0 כאל "כל אנשי הקשר נשמרו" תאבד נמענים בשקט.
const result = await textme({
newCL: {
user: { username: 'Leeroy' },
cl: [{
name: 'august-collection',
destinations: {
destination: customers.map((c) => ({
phone: c.phone,
df1: c.firstName,
df2: c.lastName,
df3: c.city,
})),
},
}],
},
})
// `identifiers` הוא המקום היחיד שבו המזהה החדש מופיע — שמרו אותו.
const listId = Array.isArray(result.identifiers)
? result.identifiers[0]
: result.identifiers
// status 0 לא אומר שכל שורה נכנסה.
for (const rejected of result.errors ?? []) {
console.warn('contact rejected:', rejected)
}result = textme({
"newCL": {
"user": {"username": "Leeroy"},
"cl": [{
"name": "august-collection",
"destinations": {
"destination": [
{
"phone": c.phone,
"df1": c.first_name,
"df2": c.last_name,
"df3": c.city,
}
for c in customers
]
},
}],
}
})
# `identifiers` הוא המקום היחיד שבו המזהה החדש מופיע — שמרו אותו.
identifiers = result["identifiers"]
list_id = identifiers[0] if isinstance(identifiers, list) else identifiers
# status 0 לא אומר שכל שורה נכנסה.
for rejected in result.get("errors", []):
print("contact rejected:", rejected)<?php
$result = textme([
'newCL' => [
'user' => ['username' => 'Leeroy'],
'cl' => [[
'name' => 'august-collection',
'destinations' => [
'destination' => array_map(fn ($c) => [
'phone' => $c->phone,
'df1' => $c->firstName,
'df2' => $c->lastName,
'df3' => $c->city,
], $customers),
],
]],
],
]);
// `identifiers` הוא המקום היחיד שבו המזהה החדש מופיע — שמרו אותו.
$listId = is_array($result['identifiers'])
? $result['identifiers'][0]
: $result['identifiers'];
// status 0 לא אומר שכל שורה נכנסה.
foreach ($result['errors'] ?? [] as $rejected) {
error_log("contact rejected: {$rejected}");
}צעד 2 — שליחה לרשימה
cl_id הוא יעד כמו כל יעד אחר. זה שולח את אותו גוף הודעה לכל מי שברשימה 21518:
<destinations>
<cl_id>21518</cl_id>
</destinations>{
"destinations": { "cl_id": "21518" }
}אפשר לערבב רשימות ומספרים בודדים בחופשיות באותה שליחה — שתי רשימות וכמה מספרים נוספים הם בלוק destinations תקין.
צעד 3 — פרסונליזציה עם שדות דינמיים
הפנו לערכים השמורים לפי מקום בגוף ההודעה, והגדירו add_dynamic בערך 1:
<?xml version="1.0" encoding="UTF-8"?>
<sms>
<user>
<username>Leeroy</username>
</user>
<source>DemoAPI</source>
<destinations>
<cl_id>21518</cl_id>
</destinations>
<message>Hello [DYNAMIC_FIELD1] [DYNAMIC_FIELD2], your order is ready for collection in [DYNAMIC_FIELD3].</message>
<add_dynamic>1</add_dynamic>
<campaign_name>august-collection</campaign_name>
</sms>{
"sms": {
"user": { "username": "Leeroy" },
"source": "DemoAPI",
"destinations": { "cl_id": "21518" },
"message": "Hello [DYNAMIC_FIELD1] [DYNAMIC_FIELD2], your order is ready for collection in [DYNAMIC_FIELD3].",
"add_dynamic": "1",
"campaign_name": "august-collection"
}
}עם df1 = Israel, df2 = Israeli, df3 = Haifa, איש הקשר הזה מקבל:
Hello Israel Israeli, your order is ready for collection in Haifa.
ל-add_dynamic יש דרישות נוקשות
cl_idאחד בדיוק. לא ניתן למזג שתי רשימות בשליחה מפורסנת אחת.- אף אלמנט
phoneבודד. למספרים בודדים אין שדות דינמיים, ולכן ערבוב נדחה.
כל ערך אחר מלבד 1 נקרא כ"כבוי", והמציינים יוצאים כטקסט מילולי — וזה מצב הכשל שכדאי להיזהר ממנו. שלחו לעצמכם פעם אחת לפני שליחה לרשימה.
אורך ושדות ריקים
הערך הממוזג נספר במגבלת 1005 התווים, והאורך משתנה בין נמענים. תבנית שנכנסת בנוחות עבור Dan לא בהכרח תיכנס עבור שם ארוך — השאירו מרווח.
איש קשר בלי df1 מקבל החלפה בערך ריק, ולכן Hello [DYNAMIC_FIELD1], הופך ל-Hello ,. שמרו ערך ברירת מחדל הגיוני בזמן בניית הרשימה ולא תקוו לטוב; אין תחביר לערך ברירת מחדל.
צעד 4 — לשמור את הרשימה מעודכנת
הוספת אנשי קשר
<?xml version="1.0" encoding="UTF-8"?>
<addNumCL>
<user>
<username>xxxxxx</username>
</user>
<cl>
<id>21518</id>
<destinations>
<destination>
<phone>055XXXXXXX</phone>
<df1>Israel</df1>
<df2>Israeli</df2>
<df3>Haifa</df3>
</destination>
<destination>
<phone>55XXXXXXX</phone>
</destination>
</destinations>
</cl>
<cl>
<id>21500</id>
<destinations>
<destination>
<phone>055XXXXXXX</phone>
</destination>
</destinations>
</cl>
</addNumCL>{
"addNumCL": {
"user": {
"username": "xxxxxx"
},
"cl": [
{
"id": "21518",
"destinations": {
"destination": [
{
"phone": "055XXXXXXX",
"df1": "Israel",
"df2": "Israeli",
"df3": "Haifa"
},
{
"phone": "55XXXXXXX"
}
]
}
},
{
"id": "21500",
"destinations": {
"destination": [
{
"phone": "055XXXXXXX"
}
]
}
}
]
}
}curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"addNumCL": {
"user": {
"username": "xxxxxx"
},
"cl": [
{
"id": "21518",
"destinations": {
"destination": [
{
"phone": "055XXXXXXX",
"df1": "Israel",
"df2": "Israeli",
"df3": "Haifa"
},
{
"phone": "55XXXXXXX"
}
]
}
},
{
"id": "21500",
"destinations": {
"destination": [
{
"phone": "055XXXXXXX"
}
]
}
}
]
}
}'// Node.js 18+ / דפדפנים — ללא תלויות
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({
addNumCL: {
user: {
username: 'xxxxxx',
},
cl: [
{
id: '21518',
destinations: {
destination: [
{
phone: '055XXXXXXX',
df1: 'Israel',
df2: 'Israeli',
df3: 'Haifa',
},
{
phone: '55XXXXXXX',
},
],
},
},
{
id: '21500',
destinations: {
destination: [
{
phone: '055XXXXXXX',
},
],
},
},
],
},
}),
})
const result = await response.json()
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if (Number(result.status) !== 0) {
throw new Error(`TextMe ${result.status}: ${result.message}`)
}
console.log(result)<?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' => [
'addNumCL' => [
'user' => [
'username' => 'xxxxxx',
],
'cl' => [
[
'id' => '21518',
'destinations' => [
'destination' => [
[
'phone' => '055XXXXXXX',
'df1' => 'Israel',
'df2' => 'Israeli',
'df3' => 'Haifa',
],
[
'phone' => '55XXXXXXX',
],
],
],
],
[
'id' => '21500',
'destinations' => [
'destination' => [
[
'phone' => '055XXXXXXX',
],
],
],
],
],
],
],
]);
$result = json_decode($response->getBody()->getContents(), true);
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if ((int) $result['status'] !== 0) {
throw new RuntimeException("TextMe {$result['status']}: {$result['message']}");
}
print_r($result);<?php
use Illuminate\Support\Facades\Http;
$result = Http::withToken(config('services.textme.token'))
->acceptJson()
->post('https://my.textme.co.il/api', [
'addNumCL' => [
'user' => [
'username' => 'xxxxxx',
],
'cl' => [
[
'id' => '21518',
'destinations' => [
'destination' => [
[
'phone' => '055XXXXXXX',
'df1' => 'Israel',
'df2' => 'Israeli',
'df3' => 'Haifa',
],
[
'phone' => '55XXXXXXX',
],
],
],
],
[
'id' => '21500',
'destinations' => [
'destination' => [
[
'phone' => '055XXXXXXX',
],
],
],
],
],
],
])
->throw()
->json();
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
throw_if((int) $result['status'] !== 0, RuntimeException::class,
"TextMe {$result['status']}: {$result['message']}");
logger()->info('TextMe', $result);# 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={
"addNumCL": {
"user": {
"username": "xxxxxx",
},
"cl": [
{
"id": "21518",
"destinations": {
"destination": [
{
"phone": "055XXXXXXX",
"df1": "Israel",
"df2": "Israeli",
"df3": "Haifa",
},
{
"phone": "55XXXXXXX",
},
],
},
},
{
"id": "21500",
"destinations": {
"destination": [
{
"phone": "055XXXXXXX",
},
],
},
},
],
},
},
)
response.raise_for_status()
result = response.json()
# גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if int(result["status"]) != 0:
raise RuntimeError(f"TextMe {result['status']}: {result['message']}")
print(result)package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
payload, _ := json.Marshal(map[string]any{
"addNumCL": map[string]any{
"user": map[string]any{
"username": "xxxxxx",
},
"cl": []any{
map[string]any{
"id": "21518",
"destinations": map[string]any{
"destination": []any{
map[string]any{
"phone": "055XXXXXXX",
"df1": "Israel",
"df2": "Israeli",
"df3": "Haifa",
},
map[string]any{
"phone": "55XXXXXXX",
},
},
},
},
map[string]any{
"id": "21500",
"destinations": map[string]any{
"destination": []any{
map[string]any{
"phone": "055XXXXXXX",
},
},
},
},
},
},
})
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)
}
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if result.Status.String() != "0" {
panic(fmt.Sprintf("TextMe %s: %s", result.Status, result.Message))
}
fmt.Println(result.Message)
}// Java 17+ — java.net.http, ללא תלויות (פענוח עם Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class TextMeClAddNumbers {
public static void main(String[] args) throws Exception {
String body = """
{
"addNumCL": {
"user": {
"username": "xxxxxx"
},
"cl": [
{
"id": "21518",
"destinations": {
"destination": [
{
"phone": "055XXXXXXX",
"df1": "Israel",
"df2": "Israeli",
"df3": "Haifa"
},
{
"phone": "55XXXXXXX"
}
]
}
},
{
"id": "21500",
"destinations": {
"destination": [
{
"phone": "055XXXXXXX"
}
]
}
}
]
}
}
""";
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());
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
System.out.println(response.body());
}
}// .NET 8+ — System.Net.Http
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var payload = """
{
"addNumCL": {
"user": {
"username": "xxxxxx"
},
"cl": [
{
"id": "21518",
"destinations": {
"destination": [
{
"phone": "055XXXXXXX",
"df1": "Israel",
"df2": "Israeli",
"df3": "Haifa"
},
{
"phone": "55XXXXXXX"
}
]
}
},
{
"id": "21500",
"destinations": {
"destination": [
{
"phone": "055XXXXXXX"
}
]
}
}
]
}
}
""";
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();
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if (status != "0")
{
var message = result.GetProperty("message").ToString();
throw new Exception($"TextMe {status}: {message}");
}
Console.WriteLine(result);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({
"addNumCL" => {
"user" => {
"username" => "xxxxxx",
},
"cl" => [
{
"id" => "21518",
"destinations" => {
"destination" => [
{
"phone" => "055XXXXXXX",
"df1" => "Israel",
"df2" => "Israeli",
"df3" => "Haifa",
},
{
"phone" => "55XXXXXXX",
},
],
},
},
{
"id" => "21500",
"destinations" => {
"destination" => [
{
"phone" => "055XXXXXXX",
},
],
},
},
],
},
})
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
result = JSON.parse(response.body)
# גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
raise "TextMe #{result['status']}: #{result['message']}" unless result["status"].to_i.zero?
pp result// [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!({
"addNumCL": {
"user": {
"username": "xxxxxx"
},
"cl": [
{
"id": "21518",
"destinations": {
"destination": [
{
"phone": "055XXXXXXX",
"df1": "Israel",
"df2": "Israeli",
"df3": "Haifa"
},
{
"phone": "55XXXXXXX"
}
]
}
},
{
"id": "21500",
"destinations": {
"destination": [
{
"phone": "055XXXXXXX"
}
]
}
}
]
}
}))
.send()
.await?
.json()
.await?;
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if result["status"] != 0 {
return Err(format!("TextMe {}: {}", result["status"], result["message"]).into());
}
println!("{result}");
Ok(())
}מספרים שכבר ברשימה חוזרים ב-errors ומדולגים, ולכן הוספה חוזרת בטוחה.
הסרת אנשי קשר
שימו לב להבדל במבנה — rmNumCL שמה אלמנטי phone ישירות בתוך destinations, בלי עוטף destination:
<?xml version="1.0" encoding="UTF-8"?>
<rmNumCL>
<user>
<username>xxxxxx</username>
</user>
<cl>
<id>21518</id>
<destinations>
<phone>055XXXXXXX</phone>
</destinations>
</cl>
<cl>
<id>21500</id>
<destinations>
<phone>055XXXXXXX</phone>
<phone>55XXXXXXX</phone>
</destinations>
</cl>
</rmNumCL>{
"rmNumCL": {
"user": {
"username": "xxxxxx"
},
"cl": [
{
"id": "21518",
"destinations": {
"phone": "055XXXXXXX"
}
},
{
"id": "21500",
"destinations": {
"phone": [
"055XXXXXXX",
"55XXXXXXX"
]
}
}
]
}
}curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"rmNumCL": {
"user": {
"username": "xxxxxx"
},
"cl": [
{
"id": "21518",
"destinations": {
"phone": "055XXXXXXX"
}
},
{
"id": "21500",
"destinations": {
"phone": [
"055XXXXXXX",
"55XXXXXXX"
]
}
}
]
}
}'// Node.js 18+ / דפדפנים — ללא תלויות
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({
rmNumCL: {
user: {
username: 'xxxxxx',
},
cl: [
{
id: '21518',
destinations: {
phone: '055XXXXXXX',
},
},
{
id: '21500',
destinations: {
phone: [
'055XXXXXXX',
'55XXXXXXX',
],
},
},
],
},
}),
})
const result = await response.json()
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if (Number(result.status) !== 0) {
throw new Error(`TextMe ${result.status}: ${result.message}`)
}
console.log(result)<?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' => [
'rmNumCL' => [
'user' => [
'username' => 'xxxxxx',
],
'cl' => [
[
'id' => '21518',
'destinations' => [
'phone' => '055XXXXXXX',
],
],
[
'id' => '21500',
'destinations' => [
'phone' => [
'055XXXXXXX',
'55XXXXXXX',
],
],
],
],
],
],
]);
$result = json_decode($response->getBody()->getContents(), true);
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if ((int) $result['status'] !== 0) {
throw new RuntimeException("TextMe {$result['status']}: {$result['message']}");
}
print_r($result);<?php
use Illuminate\Support\Facades\Http;
$result = Http::withToken(config('services.textme.token'))
->acceptJson()
->post('https://my.textme.co.il/api', [
'rmNumCL' => [
'user' => [
'username' => 'xxxxxx',
],
'cl' => [
[
'id' => '21518',
'destinations' => [
'phone' => '055XXXXXXX',
],
],
[
'id' => '21500',
'destinations' => [
'phone' => [
'055XXXXXXX',
'55XXXXXXX',
],
],
],
],
],
])
->throw()
->json();
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
throw_if((int) $result['status'] !== 0, RuntimeException::class,
"TextMe {$result['status']}: {$result['message']}");
logger()->info('TextMe', $result);# 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={
"rmNumCL": {
"user": {
"username": "xxxxxx",
},
"cl": [
{
"id": "21518",
"destinations": {
"phone": "055XXXXXXX",
},
},
{
"id": "21500",
"destinations": {
"phone": [
"055XXXXXXX",
"55XXXXXXX",
],
},
},
],
},
},
)
response.raise_for_status()
result = response.json()
# גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if int(result["status"]) != 0:
raise RuntimeError(f"TextMe {result['status']}: {result['message']}")
print(result)package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
payload, _ := json.Marshal(map[string]any{
"rmNumCL": map[string]any{
"user": map[string]any{
"username": "xxxxxx",
},
"cl": []any{
map[string]any{
"id": "21518",
"destinations": map[string]any{
"phone": "055XXXXXXX",
},
},
map[string]any{
"id": "21500",
"destinations": map[string]any{
"phone": []any{
"055XXXXXXX",
"55XXXXXXX",
},
},
},
},
},
})
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)
}
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if result.Status.String() != "0" {
panic(fmt.Sprintf("TextMe %s: %s", result.Status, result.Message))
}
fmt.Println(result.Message)
}// Java 17+ — java.net.http, ללא תלויות (פענוח עם Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class TextMeClRemoveNumbers {
public static void main(String[] args) throws Exception {
String body = """
{
"rmNumCL": {
"user": {
"username": "xxxxxx"
},
"cl": [
{
"id": "21518",
"destinations": {
"phone": "055XXXXXXX"
}
},
{
"id": "21500",
"destinations": {
"phone": [
"055XXXXXXX",
"55XXXXXXX"
]
}
}
]
}
}
""";
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());
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
System.out.println(response.body());
}
}// .NET 8+ — System.Net.Http
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var payload = """
{
"rmNumCL": {
"user": {
"username": "xxxxxx"
},
"cl": [
{
"id": "21518",
"destinations": {
"phone": "055XXXXXXX"
}
},
{
"id": "21500",
"destinations": {
"phone": [
"055XXXXXXX",
"55XXXXXXX"
]
}
}
]
}
}
""";
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();
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if (status != "0")
{
var message = result.GetProperty("message").ToString();
throw new Exception($"TextMe {status}: {message}");
}
Console.WriteLine(result);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({
"rmNumCL" => {
"user" => {
"username" => "xxxxxx",
},
"cl" => [
{
"id" => "21518",
"destinations" => {
"phone" => "055XXXXXXX",
},
},
{
"id" => "21500",
"destinations" => {
"phone" => [
"055XXXXXXX",
"55XXXXXXX",
],
},
},
],
},
})
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
result = JSON.parse(response.body)
# גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
raise "TextMe #{result['status']}: #{result['message']}" unless result["status"].to_i.zero?
pp result// [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!({
"rmNumCL": {
"user": {
"username": "xxxxxx"
},
"cl": [
{
"id": "21518",
"destinations": {
"phone": "055XXXXXXX"
}
},
{
"id": "21500",
"destinations": {
"phone": [
"055XXXXXXX",
"55XXXXXXX"
]
}
}
]
}
}))
.send()
.await?
.json()
.await?;
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if result["status"] != 0 {
return Err(format!("TextMe {}: {}", result["status"], result["message"]).into());
}
println!("{result}");
Ok(())
}שינוי שדות דינמיים של איש קשר
אין פעולת עדכון. הסירו את המספר והוסיפו אותו מחדש עם הערכים החדשים:
// אין עדכון: הוספת מספר קיים נדחית ולא ממוזגת.
await textme({
rmNumCL: {
user: { username: 'Leeroy' },
cl: [{ id: listId, destinations: { phone: contact.phone } }],
},
})
await textme({
addNumCL: {
user: { username: 'Leeroy' },
cl: [{
id: listId,
destinations: {
destination: [{ phone: contact.phone, df1: contact.firstName, df3: contact.city }],
},
}],
},
})# אין עדכון: הוספת מספר קיים נדחית ולא ממוזגת.
textme({
"rmNumCL": {
"user": {"username": "Leeroy"},
"cl": [{"id": list_id, "destinations": {"phone": contact.phone}}],
}
})
textme({
"addNumCL": {
"user": {"username": "Leeroy"},
"cl": [{
"id": list_id,
"destinations": {
"destination": [
{"phone": contact.phone, "df1": contact.first_name, "df3": contact.city}
]
},
}],
}
})יש פער בין שתי הקריאות שבו איש הקשר לא נמצא ברשימה. ברשימה שמשמשת קמפיינים מתוזמנים, בצעו את השינויים היטב לפני חלון השליחה.
צעד 5 — קריאת רשימות
getCL מחזירה כל רשימה בחשבון, אבל כדאי להכיר את המבנה לפני שקוראים לה: שורה אחת לכל איש קשר, לא לכל רשימה, כאשר cl_id ו-name חוזרים בכל שורה.
<?xml version="1.0" encoding="UTF-8"?>
<getCL>
<user>
<username>xxxxxx</username>
</user>
</getCL>{
"getCL": {
"user": {
"username": "xxxxxx"
}
}
}curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"getCL": {
"user": {
"username": "xxxxxx"
}
}
}'// Node.js 18+ / דפדפנים — ללא תלויות
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({
getCL: {
user: {
username: 'xxxxxx',
},
},
}),
})
const result = await response.json()
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if (Number(result.status) !== 0) {
throw new Error(`TextMe ${result.status}: ${result.message}`)
}
console.log(result)<?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' => [
'getCL' => [
'user' => [
'username' => 'xxxxxx',
],
],
],
]);
$result = json_decode($response->getBody()->getContents(), true);
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if ((int) $result['status'] !== 0) {
throw new RuntimeException("TextMe {$result['status']}: {$result['message']}");
}
print_r($result);<?php
use Illuminate\Support\Facades\Http;
$result = Http::withToken(config('services.textme.token'))
->acceptJson()
->post('https://my.textme.co.il/api', [
'getCL' => [
'user' => [
'username' => 'xxxxxx',
],
],
])
->throw()
->json();
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
throw_if((int) $result['status'] !== 0, RuntimeException::class,
"TextMe {$result['status']}: {$result['message']}");
logger()->info('TextMe', $result);# 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={
"getCL": {
"user": {
"username": "xxxxxx",
},
},
},
)
response.raise_for_status()
result = response.json()
# גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if int(result["status"]) != 0:
raise RuntimeError(f"TextMe {result['status']}: {result['message']}")
print(result)package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
payload, _ := json.Marshal(map[string]any{
"getCL": map[string]any{
"user": map[string]any{
"username": "xxxxxx",
},
},
})
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)
}
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if result.Status.String() != "0" {
panic(fmt.Sprintf("TextMe %s: %s", result.Status, result.Message))
}
fmt.Println(result.Message)
}// Java 17+ — java.net.http, ללא תלויות (פענוח עם Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class TextMeClGetAll {
public static void main(String[] args) throws Exception {
String body = """
{
"getCL": {
"user": {
"username": "xxxxxx"
}
}
}
""";
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());
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
System.out.println(response.body());
}
}// .NET 8+ — System.Net.Http
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var payload = """
{
"getCL": {
"user": {
"username": "xxxxxx"
}
}
}
""";
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();
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if (status != "0")
{
var message = result.GetProperty("message").ToString();
throw new Exception($"TextMe {status}: {message}");
}
Console.WriteLine(result);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({
"getCL" => {
"user" => {
"username" => "xxxxxx",
},
},
})
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
result = JSON.parse(response.body)
# גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
raise "TextMe #{result['status']}: #{result['message']}" unless result["status"].to_i.zero?
pp result// [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!({
"getCL": {
"user": {
"username": "xxxxxx"
}
}
}))
.send()
.await?
.json()
.await?;
// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
if result["status"] != 0 {
return Err(format!("TextMe {}: {}", result["status"], result["message"]).into());
}
println!("{result}");
Ok(())
}<?xml version="1.0" encoding="UTF-8"?>
<getCL>
<status>0</status>
<message></message>
<contact_lists>
<contact_list>
<cl_id>21518</cl_id>
<phone>55XXXXXXX</phone>
<name>name1</name>
</contact_list>
<contact_list>
<cl_id>21500</cl_id>
<phone>055XXXXXXX</phone>
<name>name2</name>
</contact_list>
</contact_lists>
</getCL>{
"status": 0,
"message": "",
"contact_lists": [
{
"cl_id": "21518",
"phone": "55XXXXXXX",
"name": "name1"
},
{
"cl_id": "21500",
"phone": "555XXXXXX",
"name": "name2"
}
]
}קבצו לפי cl_id כדי לשחזר רשימות:
const { contact_lists: rows } = await textme({
getCL: { user: { username: 'Leeroy' } },
})
// שורה אחת לכל איש קשר — קפלו אותן בחזרה לרשימות.
const lists = new Map()
for (const row of rows) {
if (!lists.has(row.cl_id)) {
lists.set(row.cl_id, { id: row.cl_id, name: row.name, phones: [] })
}
lists.get(row.cl_id).phones.push(row.phone)
}from collections import defaultdict
rows = textme({"getCL": {"user": {"username": "Leeroy"}}})["contact_lists"]
# שורה אחת לכל איש קשר — קפלו אותן בחזרה לרשימות.
lists = defaultdict(lambda: {"name": None, "phones": []})
for row in rows:
entry = lists[row["cl_id"]]
entry["name"] = row["name"]
entry["phones"].append(row["phone"])התשובה גדלה עם אנשי הקשר, לא עם הרשימות
חשבון עם 50,000 אנשי קשר מחזיר 50,000 שורות, לא משנה מה שאלתם. שמרו את ה-cl_id מ-newCL בזמן היצירה ולא תצטרכו את getCL כמעט לעולם; כשרוצים רשימה אחת בלבד, getCLbyID זולה בהרבה.
דברים שמפתיעים
שדות דינמיים שייכים לחברות ברשימה, לא לאיש הקשר. אותו מספר בשתי רשימות נושא ערכי df1–df6 עצמאיים. זה שימושי — פורמלי ברשימה אחת, ידידותי באחרת — אבל זה אומר שעדכון ערך הוא פעולה לכל רשימה בנפרד.
הקריאה אינה מחזירה שדות דינמיים. getCL ו-getCLbyID עונות עם cl_id, phone ו-name. ערכי df1–df6 השמורים אינם מוחזרים, ולכן TextMe לא יכולה להיות מערכת הרשומות שלכם עבורם. שמרו את העותק המחייב אצלכם.
רשימת החסימה עדיין חלה. שליחה לרשימה אינה עוקפת הסרות; חברים חסומים מושמטים בזמן השליחה. אם כל החברים חסומים, השליחה נכשלת בסטטוס 8.
מחיקת רשימה אינה מוחקת את ההיסטוריה של אנשי הקשר. דוחות מסירה של הודעות שכבר נשלחו נשארים ניתנים לשליפה לפי המזהים החיצוניים שלהם.
הצעד הבא
- שליחה מרובה ופרסונליזציה — מתי להשתמש בתבנית מרשימה ומתי להרכיב כל הודעה
- קמפיינים ותזמון — שליחה לרשימה לפי לוח זמנים
- הסרה ורגולציה — שמירה על חוקיות הרשימות כשאנשים עוזבים

