שליחה מרובה ופרסונליזציה
שליחת הודעה אחת לאנשים רבים היא sms. שליחת הודעות שונות דורשת החלטה: לבנות כל אחת בעצמכם עם bulk, או לשמור את החלקים המשתנים ברשימת תפוצה ולתת ל-TextMe למזג אותם.
איזו גישה?
bulk | sms + add_dynamic | |
|---|---|---|
| איפה הנתונים המשתנים שמורים | במסד הנתונים שלכם | ברשימת התפוצה של TextMe |
| גוף ההודעה | אתם מרכיבים כל אחת | תבנית אחת, מיזוג בצד השרת |
| שולח שונה לכל נמען | כן — לכל הודעה source משלה | לא — source אחד לכל השליחה |
| קישורים מקוצרים לכל נמען | כן | לא |
| תקרת האצווה | 2,500 אובייקטי הודעה לכל קריאה | מה שהרשימה מכילה |
| מתאים ל | טרנזקציונלי: מספרי הזמנה, קודים חד-פעמיים, חלונות מסירה | שיווקי: תבנית עם שם ועיר בתוכה |
כלל אצבע: אם הערך המשתנה בא ממערכת רשומות שכבר בשליטתכם, השתמשו ב-bulk. אם זו תכונה קבועה של איש הקשר — שם פרטי, סניף, עיר — רשימת התפוצה דורשת פחות עבודה ופחות נפח בהרבה.
גישה 1 — bulk
קריאה אחת, עד 2,500 אובייקטי הודעה, כל אחד עם הטקסט, השולח והיעדים שלו.
<?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>{
"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"
}
}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"
}
}'// 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
// 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
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);# 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)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 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());
}
}// .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);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// [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 version="1.0" encoding="UTF-8"?>
<sms>
<status>0</status>
<message>SMS bulk will be sent</message>
<shipment_id>xxxxxxx</shipment_id>
</sms>{
"status": 0,
"message": "SMS bulk will be sent",
"shipment_id": "XXXXXXX"
}בניית האצווה
החלק המכני הוא הפיכת השורות שלכם לאובייקטי הודעה. ההגדרות ברמת האצווה — תזמון, סינון לפי מועד שליחה אחרון, שם קמפיין — יושבות מחוץ ל-messages:
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)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
$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 — ואז ביטול אחד לפי שם עוצר את כל הריצה אם צריך.
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)
}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 — שדות דינמיים
כשהחלק המשתנה הוא תכונה של איש הקשר ולא של אירוע, שמרו אותו פעם אחת והשתמשו בתבנית להודעה.
כל איש קשר ברשימה נושא עד שישה ערכים, df1–df6, שמופנים אליהם בגוף ההודעה כ-[DYNAMIC_FIELD1] … [DYNAMIC_FIELD6]:
<destination>
<phone>055XXXXXXX</phone>
<df1>Israel</df1>
<df2>Israeli</df2>
<df3>Haifa</df3>
</destination>{
"destination": {
"phone": "055XXXXXXX",
"df1": "Israel",
"df2": "Israeli",
"df3": "Haifa"
}
}ואז שולחים הודעה אחת עם תבנית לרשימה:
<?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>{
"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 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>{
"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"
}
}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"
}
}'// 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
// 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
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);# 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)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 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());
}
}// .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);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// [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. הוא מוחל לכל יעד בזמן השליחה, כך שערך אחד מכסה קהל שהורכב מכמה מקורות:
{ "temp_bl": "3" }אם הוא מסנן את כולם, הקריאה נכשלת בסטטוס 715 ולא שולחת כלום בשקט — וזו ההתנהגות הרצויה, כי "שום דבר לא נשלח" ו"הכול דוכא" דורשים תגובות שונות.
הצעד הבא
- עבודה עם רשימות תפוצה — בניית הרשימות שמאפשרות את
add_dynamic - מעקב אחר מסירה — הפיכת מזהים חיצוניים לתוצאות מותאמות
- קמפיינים ותזמון — שליחת אצווה במועד מאוחר, וביטול שלה

