קמפיינים ותזמון
כל שליחה יוצרת קמפיין. תנו לו שם ומועד שליחה ותקבלו שני דברים ששווים להחזיק: ידית לביטול, ותווית שמופיעה בדוחות החשבון.
מתן שם לקמפיין
campaign_name הוא אופציונלי, עד 50 תווים, וזמין גם ב-sms וגם ב-bulk.
<campaign_name>august-newsletter-2026-08-17</campaign_name>{ "campaign_name": "august-newsletter-2026-08-17" }הפכו את השמות לייחודיים לכל ריצה
ביטול לפי שם מבטל כל קמפיין ממתין שתואם — בלי אישור, בלי הרצה יבשה. שם כמו newsletter יוריד יום אחד שליחה שלא התכוונתם לגעת בה.
שימו את התאריך, או מזהה ריצה, בשם: newsletter-2026-08-17 ולא newsletter.
בשימוש נכון, אותה התנהגות עצמה היא היתרון. ריצת bulk גדולה שפוצלה לכמה קריאות חולקת campaign_name אחד, וביטול אחד עוצר את כולה.
תזמון שליחה
timing דוחה שליחה. הפורמט הוא dd/mm/yy hh:mm — שנה בשתי ספרות, שעון 24 שעות:
<?xml version="1.0" encoding="UTF-8"?>
<sms>
<user>
<username>Leeroy</username>
</user>
<source>DemoAPI</source>
<destinations>
<cl_id>21518</cl_id>
</destinations>
<message>Doors open at 10:00 tomorrow.</message>
<timing>18/08/26 08:00</timing>
<campaign_name>opening-reminder-2026-08-18</campaign_name>
</sms>{
"sms": {
"user": { "username": "Leeroy" },
"source": "DemoAPI",
"destinations": { "cl_id": "21518" },
"message": "Doors open at 10:00 tomorrow.",
"timing": "18/08/26 08:00",
"campaign_name": "opening-reminder-2026-08-18"
}
}השמיטו את timing וההודעה תצא מיד — מה שגם אומר שהיא תעבור את נקודת הביטול לפני שהספקתם לקרוא. התזמון הוא מה שקונה לכם את החלון לשנות את דעתכם.
פרמוט חותמת הזמן
dd/mm/yy, לא mm/dd/yy ולא ISO. 18/08/26 08:00 הוא 18 באוגוסט 2026. בנו אותה במפורש ולא בהסתמכות על ברירת מחדל של locale:
| שפה | מחרוזת הפורמט |
|---|---|
| JavaScript (date-fns) | format(when, 'dd/MM/yy HH:mm') |
| Python | when.strftime("%d/%m/%y %H:%M") |
| PHP | $when->format('d/m/y H:i') |
| Java | DateTimeFormatter.ofPattern("dd/MM/yy HH:mm") |
| C# | when.ToString("dd/MM/yy HH:mm") |
| Ruby | when.strftime("%d/%m/%y %H:%M") |
| Go | when.Format("02/01/06 15:04") |
שני דברים נוספים קובעים מתי הודעה באמת יוצאת:
- שעות שליחה מותרות. לחשבונות יש חלון; שליחה מחוצה לו מחזירה סטטוס
5. שליחה מתוזמנת לשעה מחוץ לחלון היא לא רעיון טוב. temp_blמדלג על יעדים שקיבלו הודעה ב-1–14 הימים האחרונים. הוא מוערך בזמן השליחה ולא בזמן התזמון — כך שנמען ששומע מכם בין התזמון לשליחה עדיין יסונן.
ב-bulk, timing הוא שדה ברמת האצווה, מחוץ ל-messages. אי אפשר לתזמן הודעות בודדות באצווה בנפרד; שעות שונות פירושן קריאות שונות.
SOAP מתנהג אחרת
sendBulkSms דרך SOAP מתעלם מ-timing לחלוטין. רק פעולת bulk בנקודת הקצה הראשית מכבדת אותו.
ביטול
לפי מזהה
<?xml version="1.0" encoding="UTF-8"?>
<cancel>
<user>
<username>Leeroy</username>
</user>
<campaign_id>1234</campaign_id>
</cancel>{
"cancel": {
"user": {
"username": "Leeroy"
},
"campaign_id": "1234"
}
}curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"cancel": {
"user": {
"username": "Leeroy"
},
"campaign_id": "1234"
}
}'// 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({
cancel: {
user: {
username: 'Leeroy',
},
campaign_id: '1234',
},
}),
})
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' => [
'cancel' => [
'user' => [
'username' => 'Leeroy',
],
'campaign_id' => '1234',
],
],
]);
$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', [
'cancel' => [
'user' => [
'username' => 'Leeroy',
],
'campaign_id' => '1234',
],
])
->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={
"cancel": {
"user": {
"username": "Leeroy",
},
"campaign_id": "1234",
},
},
)
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{
"cancel": map[string]any{
"user": map[string]any{
"username": "Leeroy",
},
"campaign_id": "1234",
},
})
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 TextMeCampaignCancelById {
public static void main(String[] args) throws Exception {
String body = """
{
"cancel": {
"user": {
"username": "Leeroy"
},
"campaign_id": "1234"
}
}
""";
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 = """
{
"cancel": {
"user": {
"username": "Leeroy"
},
"campaign_id": "1234"
}
}
""";
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({
"cancel" => {
"user" => {
"username" => "Leeroy",
},
"campaign_id" => "1234",
},
})
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!({
"cancel": {
"user": {
"username": "Leeroy"
},
"campaign_id": "1234"
}
}))
.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"?>
<cancel>
<status>0</status>
<message>Campaign successfuly cancel</message>
</cancel>{
"status": 0,
"message": "Campaign successfuly cancel"
}לפי שם
מבטל כל מה שממתין תחת השם הזה, ומדווח כמה:
<?xml version="1.0" encoding="UTF-8"?>
<cancel>
<user>
<username>Leeroy</username>
</user>
<campaign_name>My Campaign</campaign_name>
</cancel>{
"cancel": {
"user": {
"username": "Leeroy"
},
"campaign_name": "My Campaign"
}
}curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"cancel": {
"user": {
"username": "Leeroy"
},
"campaign_name": "My 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({
cancel: {
user: {
username: 'Leeroy',
},
campaign_name: 'My 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' => [
'cancel' => [
'user' => [
'username' => 'Leeroy',
],
'campaign_name' => 'My 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', [
'cancel' => [
'user' => [
'username' => 'Leeroy',
],
'campaign_name' => 'My 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={
"cancel": {
"user": {
"username": "Leeroy",
},
"campaign_name": "My 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{
"cancel": map[string]any{
"user": map[string]any{
"username": "Leeroy",
},
"campaign_name": "My 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 TextMeCampaignCancelByName {
public static void main(String[] args) throws Exception {
String body = """
{
"cancel": {
"user": {
"username": "Leeroy"
},
"campaign_name": "My 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 = """
{
"cancel": {
"user": {
"username": "Leeroy"
},
"campaign_name": "My 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({
"cancel" => {
"user" => {
"username" => "Leeroy",
},
"campaign_name" => "My 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!({
"cancel": {
"user": {
"username": "Leeroy"
},
"campaign_name": "My 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"?>
<cancel>
<status>0</status>
<message>Campaigns successfully cancel</message>
<count>1</count>
</cancel>{
"status": 0,
"message": "Campaigns successfully cancel",
"count": 1
}count הוא כמה נעצרו. 0 עם status: 0 אומר שלא נמצאה התאמה — או שהשם שגוי, או שהקמפיינים כבר יצאו.
קריאת התוצאה
| Status | משמעות | מה לעשות |
|---|---|---|
0 | בוטל. | — |
955 | כבר בוטל. | התייחסו כהצלחה; המצב הסופי הוא מה שרציתם. |
966 | כבר נשלח. | מאוחר מדי. אין מה לעשות מלבד לתעד. |
970 | הביטול נכשל בצד השרת. | נסו שוב פעם אחת, ואז פנו לתמיכה. |
977 | מזהה שגוי, או שהוא שייך לחשבון אחר. | בדקו את המזהה. |
async function cancelCampaign(name) {
const result = await textme({
cancel: { user: { username: 'Leeroy' }, campaign_name: name },
})
// 955 אומר שהוא כבר בוטל — המצב שביקשתם, בכל מקרה.
if (Number(result.status) === 955) return { cancelled: 0, alreadyDone: true }
// 966 אומר שהוא יצא. לא ניתן לשחזור, אבל גם לא באג.
if (Number(result.status) === 966) return { cancelled: 0, tooLate: true }
if (Number(result.status) !== 0) {
throw new Error(`TextMe ${result.status}: ${result.message}`)
}
return { cancelled: Number(result.count ?? 0) }
}def cancel_campaign(name):
result = textme({
"cancel": {"user": {"username": "Leeroy"}, "campaign_name": name}
})
status = int(result["status"])
# 955 אומר שהוא כבר בוטל — המצב שביקשתם, בכל מקרה.
if status == 955:
return {"cancelled": 0, "already_done": True}
# 966 אומר שהוא יצא. לא ניתן לשחזור, אבל גם לא באג.
if status == 966:
return {"cancelled": 0, "too_late": True}
if status != 0:
raise RuntimeError(f"TextMe {status}: {result['message']}")
return {"cancelled": int(result.get("count", 0))}תבנית בטוחה לשליחה מתוזמנת
תזמנו מוקדם מספיק כדי שביטול יהיה אפשרי באמת, ותנו שם לריצה כך שהביטול יהיה קריאה אחת:
const runId = `newsletter-${new Date().toISOString().slice(0, 10)}`
// 1. האם יש מספיק יתרה לכל הקהל?
const balance = await textme({ balance: { user: { username: 'Leeroy' } } })
if (Number(balance.balance) < audience.length) {
throw new Error(`Need ${audience.length} credits, have ${balance.balance}`)
}
// 2. אמתו את גוף הבקשה המדויק מול /api/test לפני שמתחייבים אליו.
await textmeTest(payload(runId))
// 3. תזמנו אותו, עם מזהה הריצה כשם הקמפיין.
const { shipment_id } = await textme(payload(runId))
await db.recordScheduledRun({ runId, shipmentId: shipment_id })
// בהמשך, אם משהו לא בסדר: קריאה אחת עוצרת כל פלח של הריצה.
await textme({ cancel: { user: { username: 'Leeroy' }, campaign_name: runId } })from datetime import date
run_id = f"newsletter-{date.today().isoformat()}"
# 1. האם יש מספיק יתרה לכל הקהל?
balance = textme({"balance": {"user": {"username": "Leeroy"}}})
if int(balance["balance"]) < len(audience):
raise RuntimeError(f"Need {len(audience)} credits, have {balance['balance']}")
# 2. אמתו את גוף הבקשה המדויק מול /api/test לפני שמתחייבים אליו.
textme_test(payload(run_id))
# 3. תזמנו אותו, עם מזהה הריצה כשם הקמפיין.
result = textme(payload(run_id))
db.record_scheduled_run(run_id=run_id, shipment_id=result["shipment_id"])
# בהמשך, אם משהו לא בסדר: קריאה אחת עוצרת כל פלח של הריצה.
textme({"cancel": {"user": {"username": "Leeroy"}, "campaign_name": run_id}})קמפייני יום הולדת
שליחות חוזרות שמופעלות לפי תאריך הלידה של איש קשר. הם נוצרים בממשק הניהול; ה-API מציג אותם ומחליף את ההודעה שלהם.
<?xml version="1.0" encoding="UTF-8"?>
<get_birthday_campaigns>
<user>
<username>XXXXXX</username>
</user>
</get_birthday_campaigns>{
"get_birthday_campaigns": {
"user": {
"username": "XXXXXX"
}
}
}curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"get_birthday_campaigns": {
"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({
get_birthday_campaigns: {
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' => [
'get_birthday_campaigns' => [
'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', [
'get_birthday_campaigns' => [
'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={
"get_birthday_campaigns": {
"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{
"get_birthday_campaigns": 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 TextMeBirthdayList {
public static void main(String[] args) throws Exception {
String body = """
{
"get_birthday_campaigns": {
"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 = """
{
"get_birthday_campaigns": {
"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({
"get_birthday_campaigns" => {
"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!({
"get_birthday_campaigns": {
"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"?>
<sms>
<status>0</status>
<message></message>
<birthday_campaigns>
<birthday_campaign>
<CAMPAIGN_ID>XXXX</CAMPAIGN_ID>
<CAMPAIGN_NAME>XXXX</CAMPAIGN_NAME>
<CREATED_ON>18/10/23 14:40:39</CREATED_ON>
<ACTIVE_DESTINATIONS>X</ACTIVE_DESTINATIONS>
</birthday_campaign>
</birthday_campaigns>
</sms>{
"status": 0,
"message": "",
"birthday_campaigns": {
"birthday_campaign": [
{
"CAMPAIGN_ID": "xxxxx",
"CAMPAIGN_NAME": "xxxxx",
"CREATED_ON": "18/10/23 14:40:39",
"ACTIVE_DESTINATIONS": ""
}
]
}
}שמות השדות כאן באותיות גדולות
CAMPAIGN_ID, CAMPAIGN_NAME, CREATED_ON, ACTIVE_DESTINATIONS — בשונה מסגנון האותיות הקטנות שנהוג בכל שאר ה-API.
ACTIVE_DESTINATIONS הוא כמה אנשי קשר הקמפיין מכוון אליהם כרגע, מה שהופך אותו לבדיקת תקינות שימושית: קמפיין יום הולדת שירד בשקט לאפס לא ישלח שום דבר.
עדכון גוף ההודעה:
<?xml version="1.0" encoding="UTF-8"?>
<edit_birthday_campaign>
<user>
<username>XXXX</username>
</user>
<campaign_id>XXXX</campaign_id>
<message>XXXX</message>
</edit_birthday_campaign>{
"edit_birthday_campaign": {
"user": {
"username": "XXXX"
},
"campaign_id": "XXXX",
"message": "XXXX"
}
}curl --location 'https://my.textme.co.il/api' \
--header "Authorization: Bearer $TEXTME_API_TOKEN" \
--header 'Content-Type: application/json' \
--data '{
"edit_birthday_campaign": {
"user": {
"username": "XXXX"
},
"campaign_id": "XXXX",
"message": "XXXX"
}
}'// 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({
edit_birthday_campaign: {
user: {
username: 'XXXX',
},
campaign_id: 'XXXX',
message: 'XXXX',
},
}),
})
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' => [
'edit_birthday_campaign' => [
'user' => [
'username' => 'XXXX',
],
'campaign_id' => 'XXXX',
'message' => 'XXXX',
],
],
]);
$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', [
'edit_birthday_campaign' => [
'user' => [
'username' => 'XXXX',
],
'campaign_id' => 'XXXX',
'message' => 'XXXX',
],
])
->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={
"edit_birthday_campaign": {
"user": {
"username": "XXXX",
},
"campaign_id": "XXXX",
"message": "XXXX",
},
},
)
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{
"edit_birthday_campaign": map[string]any{
"user": map[string]any{
"username": "XXXX",
},
"campaign_id": "XXXX",
"message": "XXXX",
},
})
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 TextMeBirthdayEdit {
public static void main(String[] args) throws Exception {
String body = """
{
"edit_birthday_campaign": {
"user": {
"username": "XXXX"
},
"campaign_id": "XXXX",
"message": "XXXX"
}
}
""";
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 = """
{
"edit_birthday_campaign": {
"user": {
"username": "XXXX"
},
"campaign_id": "XXXX",
"message": "XXXX"
}
}
""";
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({
"edit_birthday_campaign" => {
"user" => {
"username" => "XXXX",
},
"campaign_id" => "XXXX",
"message" => "XXXX",
},
})
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!({
"edit_birthday_campaign": {
"user": {
"username": "XXXX"
},
"campaign_id": "XXXX",
"message": "XXXX"
}
}))
.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>birthday campaign successfully updated</message>
</sms>{
"status": 0,
"message": "birthday campaign successfully updated"
}רק הגוף משתנה. התזמון, הקהל והשם נשארים כפי שהם, ואין פעולת API לשינוי שלהם.
איזו ידית לשמור?
שליחה מחזירה shipment_id; הביטול מקבל campaign_id. בפועל שניהם מזהים את אותו קמפיין, וביטול לפי שם הוא ההרגל העמיד יותר:
- הוא עובד על ריצה שפוצלה לכמה קריאות
bulk. - הוא לא תלוי בכך ששמרתם מזהה.
- הוא נגזר ממזהה הריצה שלכם, ולכן אפשר לבטל מסקריפט שמעולם לא ראה את התשובה המקורית.
שמרו את ה-shipment_id בכל מקרה — הוא מופיע בדוחות מסירה, ושם תרצו אותו.
הצעד הבא
- שליחה מרובה ופרסונליזציה — בניית הריצה שאתם מתזמנים
- מעקב אחר מסירה — מה קרה לאחר שהיא יצאה
- נקודת הקצה של קמפיינים — תיעוד הפעולות המלא

