Skip to content

שליחה מרובה ופרסונליזציה

שליחת הודעה אחת לאנשים רבים היא sms. שליחת הודעות שונות דורשת החלטה: לבנות כל אחת בעצמכם עם bulk, או לשמור את החלקים המשתנים ברשימת תפוצה ולתת ל-TextMe למזג אותם.

איזו גישה?

bulksms + add_dynamic
איפה הנתונים המשתנים שמוריםבמסד הנתונים שלכםברשימת התפוצה של TextMe
גוף ההודעהאתם מרכיבים כל אחתתבנית אחת, מיזוג בצד השרת
שולח שונה לכל נמעןכן — לכל הודעה source משלהלא — source אחד לכל השליחה
קישורים מקוצרים לכל נמעןכןלא
תקרת האצווה2,500 אובייקטי הודעה לכל קריאהמה שהרשימה מכילה
מתאים לטרנזקציונלי: מספרי הזמנה, קודים חד-פעמיים, חלונות מסירהשיווקי: תבנית עם שם ועיר בתוכה

כלל אצבע: אם הערך המשתנה בא ממערכת רשומות שכבר בשליטתכם, השתמשו ב-bulk. אם זו תכונה קבועה של איש הקשר — שם פרטי, סניף, עיר — רשימת התפוצה דורשת פחות עבודה ופחות נפח בהרבה.

גישה 1 — bulk

קריאה אחת, עד 2,500 אובייקטי הודעה, כל אחד עם הטקסט, השולח והיעדים שלו.

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+ / דפדפנים — ללא תלויות
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()

// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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);

// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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();

// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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()

# גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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)
	}

	// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
	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, ללא תלויות (פענוח עם 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());

        // גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
        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();

// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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)

# גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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?;

    // גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
    if result["status"] != 0 {
        return Err(format!("TextMe {}: {}", result["status"], result["message"]).into());
    }

    println!("{result}");
    Ok(())
}
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"
}

בניית האצווה

החלק המכני הוא הפיכת השורות שלכם לאובייקטי הודעה. ההגדרות ברמת האצווה — תזמון, סינון לפי מועד שליחה אחרון, שם קמפיין — יושבות מחוץ ל-messages:

js
const orders = await db.ordersReadyToShip()

const payload = {
  bulk: {
    user: { username: 'Leeroy' },
    messages: {
      sms: orders.map((order) => ({
        source: 'DemoAPI',
        destinations: {
          // המזהה החיצוני הוא מה שיאפשר להתאים בהמשך את ההודעה הזו בדיוק.
          phone: { $: { id: `order-${order.id}` }, _: order.phone },
        },
        message: `Order ${order.reference} ships today. Track it: [link-t]`,
        links: {
          link: [{ $: { id: 't' }, _: order.trackingUrl }],
        },
      })),
    },
    // ברמת האצווה, מחוץ ל-messages.
    campaign_name: `shipping-${new Date().toISOString().slice(0, 10)}`,
    temp_bl: '1',
  },
}

const result = await textme(payload)
python
from datetime import date

orders = db.orders_ready_to_ship()

payload = {
    "bulk": {
        "user": {"username": "Leeroy"},
        "messages": {
            "sms": [
                {
                    "source": "DemoAPI",
                    "destinations": {
                        # המזהה החיצוני הוא מה שיאפשר להתאים את ההודעה הזו בהמשך.
                        "phone": {"$": {"id": f"order-{order.id}"}, "_": order.phone},
                    },
                    "message": f"Order {order.reference} ships today. Track it: [link-t]",
                    "links": {"link": [{"$": {"id": "t"}, "_": order.tracking_url}]},
                }
                for order in orders
            ]
        },
        # ברמת האצווה, מחוץ ל-messages.
        "campaign_name": f"shipping-{date.today().isoformat()}",
        "temp_bl": "1",
    }
}

result = textme(payload)
php
<?php

$orders = $db->ordersReadyToShip();

$payload = [
    'bulk' => [
        'user' => ['username' => 'Leeroy'],
        'messages' => [
            'sms' => array_map(fn ($order) => [
                'source' => 'DemoAPI',
                'destinations' => [
                    // המזהה החיצוני יאפשר להתאים את ההודעה הזו בהמשך.
                    'phone' => ['$' => ['id' => "order-{$order->id}"], '_' => $order->phone],
                ],
                'message' => "Order {$order->reference} ships today. Track it: [link-t]",
                'links' => [
                    'link' => [['$' => ['id' => 't'], '_' => $order->trackingUrl]],
                ],
            ], $orders),
        ],
        // ברמת האצווה, מחוץ ל-messages.
        'campaign_name' => 'shipping-'.date('Y-m-d'),
        'temp_bl' => '1',
    ],
];

$result = textme($payload);

פיצול מעל 2,500

התקרה מונה אובייקטי הודעה, לא נמענים. פצלו ריצה גדולה לכמה קריאות ותנו לכולן את אותו campaign_name — ואז ביטול אחד לפי שם עוצר את כל הריצה אם צריך.

js
const BATCH = 2000 // מתחת לתקרת 2,500, עם מרווח ביטחון
const campaign = `shipping-${new Date().toISOString().slice(0, 10)}`
const shipments = []

for (let i = 0; i < orders.length; i += BATCH) {
  const result = await textme({
    bulk: {
      user: { username: 'Leeroy' },
      messages: { sms: orders.slice(i, i + BATCH).map(toMessage) },
      campaign_name: campaign, // משותף, כך שביטול אחד עוצר כל פלח
    },
  })

  shipments.push(result.shipment_id)
}
python
BATCH = 2000  # מתחת לתקרת 2,500, עם מרווח ביטחון
campaign = f"shipping-{date.today().isoformat()}"
shipments = []

for start in range(0, len(orders), BATCH):
    result = textme({
        "bulk": {
            "user": {"username": "Leeroy"},
            "messages": {"sms": [to_message(o) for o in orders[start:start + BATCH]]},
            "campaign_name": campaign,  # משותף, כך שביטול אחד עוצר כל פלח
        }
    })

    shipments.append(result["shipment_id"])

בדקו את היתרה קודם

אצווה שחורגת מהיתרה נכשלת כמכלול בסטטוס 4, ולא באמצע התהליך. balance לפני ריצה גדולה הופך את זה לכשל מקדים ונקי.

גישה 2 — שדות דינמיים

כשהחלק המשתנה הוא תכונה של איש הקשר ולא של אירוע, שמרו אותו פעם אחת והשתמשו בתבנית להודעה.

כל איש קשר ברשימה נושא עד שישה ערכים, df1df6, שמופנים אליהם בגוף ההודעה כ-[DYNAMIC_FIELD1][DYNAMIC_FIELD6]:

xml
<destination>
    <phone>055XXXXXXX</phone>
    <df1>Israel</df1>
    <df2>Israeli</df2>
    <df3>Haifa</df3>
</destination>
json
{
  "destination": {
    "phone": "055XXXXXXX",
    "df1": "Israel",
    "df2": "Israeli",
    "df3": "Haifa"
  }
}

ואז שולחים הודעה אחת עם תבנית לרשימה:

xml
<?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], your order is ready for collection in [DYNAMIC_FIELD3].</message>
    <add_dynamic>1</add_dynamic>
    <campaign_name>collection-reminder</campaign_name>
</sms>
json
{
  "sms": {
    "user": { "username": "Leeroy" },
    "source": "DemoAPI",
    "destinations": { "cl_id": "21518" },
    "message": "Hello [DYNAMIC_FIELD1], your order is ready for collection in [DYNAMIC_FIELD3].",
    "add_dynamic": "1",
    "campaign_name": "collection-reminder"
  }
}

ל-add_dynamic יש דרישות נוקשות

הוא עובד רק עם cl_id אחד בדיוק ואף אלמנט phone בודד. ערכי המיזוג חייבים לבוא מרשימה אחת, ולכן שליחה שמערבבת רשימה עם מספרים בודדים אינה יכולה לבצע פרסונליזציה.

פירוט מלא על בנייה ותחזוקה של רשימות נמצא בעבודה עם רשימות תפוצה.

התאמת נתונים לאצווה

בכל גישה שתבחרו, אצווה מחזירה shipment_id אחד לכל העניין. תוצאות לכל נמען מגיעות מהמזהים החיצוניים שהגדרתם:

xml
<?xml version="1.0" encoding="UTF-8"?>
<dlr>
    <user>
        <username>Leeroy</username>
    </user>
    <transactions>
        <external_id>some id 1</external_id>
        <external_id>some id 2</external_id>
    </transactions>
    <from>01/01/14 00:00</from>
    <to>01/01/14 23:59</to>
</dlr>
json
{
  "dlr": {
    "user": {
      "username": "Leeroy"
    },
    "transactions": {
      "external_id": [
        "some id 1",
        "some id 2"
      ]
    },
    "from": "01/01/14 00:00",
    "to": "01/01/14 23:59"
  }
}
bash
curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
  "dlr": {
    "user": {
      "username": "Leeroy"
    },
    "transactions": {
      "external_id": [
        "some id 1",
        "some id 2"
      ]
    },
    "from": "01/01/14 00:00",
    "to": "01/01/14 23:59"
  }
}'
js
// 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({
    dlr: {
      user: {
        username: 'Leeroy',
      },
      transactions: {
        external_id: [
          'some id 1',
          'some id 2',
        ],
      },
      from: '01/01/14 00:00',
      to: '01/01/14 23:59',
    },
  }),
})

const result = await response.json()

// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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' => [
        'dlr' => [
            'user' => [
                'username' => 'Leeroy',
            ],
            'transactions' => [
                'external_id' => [
                    'some id 1',
                    'some id 2',
                ],
            ],
            'from' => '01/01/14 00:00',
            'to' => '01/01/14 23:59',
        ],
    ],
]);

$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
<?php

use Illuminate\Support\Facades\Http;

$result = Http::withToken(config('services.textme.token'))
    ->acceptJson()
    ->post('https://my.textme.co.il/api', [
        'dlr' => [
            'user' => [
                'username' => 'Leeroy',
            ],
            'transactions' => [
                'external_id' => [
                    'some id 1',
                    'some id 2',
                ],
            ],
            'from' => '01/01/14 00:00',
            'to' => '01/01/14 23:59',
        ],
    ])
    ->throw()
    ->json();

// גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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={
        "dlr": {
            "user": {
                "username": "Leeroy",
            },
            "transactions": {
                "external_id": [
                    "some id 1",
                    "some id 2",
                ],
            },
            "from": "01/01/14 00:00",
            "to": "01/01/14 23:59",
        },
    },
)
response.raise_for_status()
result = response.json()

# גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
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{
		"dlr": map[string]any{
			"user": map[string]any{
				"username": "Leeroy",
			},
			"transactions": map[string]any{
				"external_id": []any{
					"some id 1",
					"some id 2",
				},
			},
			"from": "01/01/14 00:00",
			"to": "01/01/14 23:59",
		},
	})

	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
// 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 TextMeDlr {
    public static void main(String[] args) throws Exception {
        String body = """
            {
              "dlr": {
                "user": {
                  "username": "Leeroy"
                },
                "transactions": {
                  "external_id": [
                    "some id 1",
                    "some id 2"
                  ]
                },
                "from": "01/01/14 00:00",
                "to": "01/01/14 23:59"
              }
            }
            """;

        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());
    }
}
csharp
// .NET 8+ — System.Net.Http
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

var payload = """
    {
      "dlr": {
        "user": {
          "username": "Leeroy"
        },
        "transactions": {
          "external_id": [
            "some id 1",
            "some id 2"
          ]
        },
        "from": "01/01/14 00:00",
        "to": "01/01/14 23:59"
      }
    }
    """;

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);
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({
  "dlr" => {
    "user" => {
      "username" => "Leeroy",
    },
    "transactions" => {
      "external_id" => [
        "some id 1",
        "some id 2",
      ],
    },
    "from" => "01/01/14 00:00",
    "to" => "01/01/14 23:59",
  },
})

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
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!({
          "dlr": {
            "user": {
              "username": "Leeroy"
            },
            "transactions": {
              "external_id": [
                "some id 1",
                "some id 2"
              ]
            },
            "from": "01/01/14 00:00",
            "to": "01/01/14 23:59"
          }
        }))
        .send()
        .await?
        .json()
        .await?;

    // גם שגיאה חוזרת כ-HTTP 200 — הסטטוס שבגוף התשובה הוא הקובע
    if result["status"] != 0 {
        return Err(format!("TextMe {}: {}", result["status"], result["message"]).into());
    }

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

עם מזהים בצורה כמו order-10052, מיפוי שורת דוח בחזרה לרשומה שלכם הוא פיצול מחרוזת. בלעדיהם, אצווה של 2,000 הודעות מייצרת 2,000 שורות דוח שאפשר להתאים רק לפי מספר טלפון — עובד, אבל דו-משמעי ברגע שאדם אחד מופיע פעמיים.

מזהה אחד לכל יעד, תמיד

זה לא עולה כלום בזמן השליחה וזה הדבר היחיד שהופך ריצה גדולה לניתנת לביקורת בהמשך. הפכו את זה להרגל ולא להחלטה.

דיכוי נמענים שקיבלו לאחרונה

temp_bl מדלג על יעדים שכבר שמעו מכם ב-n הימים האחרונים, 1 עד 14. הוא מוחל לכל יעד בזמן השליחה, כך שערך אחד מכסה קהל שהורכב מכמה מקורות:

json
{ "temp_bl": "3" }

אם הוא מסנן את כולם, הקריאה נכשלת בסטטוס 715 ולא שולחת כלום בשקט — וזו ההתנהגות הרצויה, כי "שום דבר לא נשלח" ו"הכול דוכא" דורשים תגובות שונות.

הצעד הבא