קמפיינים ותזמון
כל שליחה יוצרת קמפיין. תנו לו שם ומועד שליחה ותקבלו שני דברים ששווים להחזיק: ידית לביטול, ותווית שמופיעה בדוחות החשבון.
מתן שם לקמפיין
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+ using 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+ using 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+ using 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+ using 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 בכל מקרה. הוא מופיע בדוחות מסירה, ושם תרצו אותו.
הצעד הבא
- שליחה מרובה ופרסונליזציה: בניית הריצה שאתם מתזמנים
- מעקב אחר מסירה: מה קרה לאחר שהיא יצאה
- נקודת הקצה של קמפיינים: תיעוד הפעולות המלא

