DBDelve
Dive deep into your data. A fast, native, multi-engine database client written in Rust. Free and open source, no telemetry.
翻訳待ち
No updates detected yet. GitHub releases will appear here.
PHP SDK for Sendd: send SMS to Tanzanian mobile numbers, with safe retries, previews and webhook verification (MIT).
翻訳待ち
Loading the full README.
Read the full README ↗Send SMS to Tanzanian mobile numbers with Sendd from PHP 8.1 or later.
composer config repositories.sendd vcs https://github.com/UNTU1995/sendd-php
composer require sendd/sendd
$sendd = new Sendd\Client(); // the key from SENDD_API_KEY
$result = $sendd->messages->send(
to: '0754 123 456',
from: 'MYSHOP',
body: 'Habari! Oda yako #1042 iko tayari.',
clientRef: 'order-1042',
);
echo $result->message['id'], ' ', $result->message['status']; // "accepted": recorded and paid for, not yet delivered
Start with a test key (sndd_test_…, from the dashboard's API keys page). Test messages never reach a phone and are never charged; they finish at once with an outcome set by the number's last digit: 1 or 2 failed, 8 undelivered, anything else delivered.
Needs ext-curl, ext-json and ext-mbstring, and has no other dependencies. ext-intl is suggested: with it, text is normalized to Unicode NFC before counting, exactly as Sendd counts. Without it, text is counted as given, which differs from Sendd's count only for text in decomposed form (a letter followed by a separate combining accent).
429 or a 5xx (not 501), the SDK sends again with the same key and the same message, so Sendd answers with the first result and never sends or charges twice. It waits out Retry-After up to 30 seconds (maxRetryWait); a longer wait comes back as the exception's retryAfter. A refusal (any other 4xx) is never retried. Redirects are never followed.accepted and submitted are not delivered. unknown means the outcome is unclear and Sendd is checking it: never send that message again yourself.messages->preview() counts segments exactly as Sendd will, checks the characters the SMS route takes, prices the message from your rates and checks the sender ID and your balance, as far as the key may read them.$preview = $sendd->messages->preview(body: $text, to: '0754123456', from: 'MYSHOP');
if (!$preview->ok) {
// Each problem has the code Sendd would refuse with: encoding_not_supported,
// too_many_segments, sender_not_authorized, sender_not_ready, insufficient_funds, …
foreach ($preview->problems as $problem) {
echo $problem->code, ': ', $problem->message, "\n";
}
} else {
echo "{$preview->segments} segment(s), {$preview->total} cents\n";
}
$result = $sendd->messages->send(
to: '0754123456',
from: 'MYSHOP',
body: $text,
clientRef: 'order-1042',
ttl: 300,
idempotencyKey: 'order-1042-ready', // optional: makes the send idempotent across restarts too
);
$result->message; // the message, as the API returns it (snake_case fields)
$result->idempotencyKey; // keep it to retry this exact send
$result->replayed; // true: Sendd answered with an earlier identical send's result
$final = $sendd->messages->waitForFinal($result->message['id'], timeout: 60);
The sender ID must be spelled exactly as listed by $sendd->senderIds->list(), case included.
Some routes take plain ASCII text only ($sendd->serviceStatus()['sms_route'] === 'ascii'): accented letters, curly quotes, long dashes and emoji are refused with encoding_not_supported and the exact characters and positions, and Sendd never removes or replaces them for you. Sendd\Text::countOn($body, $route) counts a body as Sendd will; Sendd\Phone::normalize($number) gives a number in E.164, or null when Sendd would refuse it.
Every failed call throws Sendd\SenddException:
use Sendd\SenddException;
try {
$sendd->messages->send(to: $to, from: 'MYSHOP', body: $text, clientRef: $ref);
} catch (SenddException $e) {
$e->errorCode; // "insufficient_funds", "rate_limited", "network_error", "timeout", "invalid_response", …
$e->status; // HTTP status, or null when no answer arrived
$e->field; // "to", "from", "body", … when the error is about one field
$e->details; // for encoding_not_supported: the characters refused
$e->retryAfter; // seconds Sendd asked to wait (429, 503)
$e->requestId; // quote it to Sendd support
$e->isRetryable(); // true: the same request may be sent again unchanged
$e->isOutcomeUnknown(); // true: Sendd may have accepted it; retry with $e->idempotencyKey
}
getCode() is always 0 (PHP's exception code is an integer); the string code is $e->errorCode (or getErrorCode()). A client set up wrongly (no key, plain http to another machine) throws invalid_configuration from the constructor.
foreach ($sendd->messages->all(['status' => 'failed']) as $m) { // every page, as a generator
echo $m['id'], ' ', $m['failure_reason'], "\n";
}
$page = $sendd->messages->list(['limit' => 50]); // $page->items, $page->nextBefore
$message = $sendd->messages->get($id);
$wallet = $sendd->wallet->get(); // cents (1/100 TZS)
$statement = $sendd->wallet->statement(['limit' => 20]);
$rates = $sendd->pricing(); // cents per segment
$senders = $sendd->senderIds->list();
$key = $sendd->key(); // mode and scopes
$campaigns = $sendd->campaigns->list();
$deliveries = $sendd->webhookDeliveries->list(['state' => 'failed']);
Each call needs the key to hold its scope: messages:send, messages:read, sender_ids:read, wallet:read, campaigns:read, webhooks:read. A missing scope is insufficient_scope (403). An unknown filter name throws InvalidArgumentException rather than being ignored.
This is the beginning of the README. Read the rest at the original. Read the full README ↗