Sending Messages Asynchronously & Concurrently
For high-volume use cases, SMS messages can be dispatched asynchronously or sent concurrently in a pool, instead of blocking on every request in a loop.
Async: async()
Chain async() before send() to dispatch a single message without blocking. Instead of a SentMessageResponse, send() returns a Guzzle PromiseInterface:
use SamuelMwangiW\Africastalking\Facades\Africastalking;
$promise = Africastalking::sms('Hello there')
->to('+254712345678')
->async()
->send();
// ... do other work while the request is in flight ...
$response = $promise->wait(); // SentMessageResponseIf the send fails — either a transport error or an API-level failure such as an invalid sender ID — the promise rejects with an AfricastalkingException instead of throwing synchronously, so wrap wait() in a try/catch:
try {
$response = $promise->wait();
} catch (\SamuelMwangiW\Africastalking\Exceptions\AfricastalkingException $exception) {
// handle the failure
}async() is only relevant to a single in-flight request. To send many messages concurrently, use pool() instead.
Concurrency: pool()
Unlike async(), calling pool() is a blocking call: it dispatches every message concurrently — capped at a maximum number of requests in flight at once — waits for all of them to finish, and only then returns. There's no separate send() or wait() step; pool() does both internally and hands you back the finished results.
use SamuelMwangiW\Africastalking\Facades\Africastalking;
$messages = [
Africastalking::sms('Hi Alice')->to('+254700000001'),
Africastalking::sms('Hi Bob')->to('+254700000002'),
Africastalking::sms('Hi Carol')->to('+254700000003'),
];
$results = Africastalking::sms()->pool($messages, concurrency: 5);
// By the time execution reaches here, every message above has already been sent.Under the hood this uses Saloon's request pool, which is itself built on Guzzle promises — but that's an implementation detail; pool() resolves the promise for you before returning.
pool() returns an Illuminate\Support\Collection keyed the same way as the input — numeric or string keys are both preserved:
$results = Africastalking::sms()->pool([
'alice' => Africastalking::sms('Hi Alice')->to('+254700000001'),
'bob' => Africastalking::sms('Hi Bob')->to('+254700000002'),
]);
$results->get('alice'); // SentMessageResponse|ThrowablePer-item failures don't fail the whole pool
Each entry in the returned collection is either a SentMessageResponse on success, or the Throwable that was raised for that specific message. Inspect each result to know which recipients succeeded:
foreach ($results as $key => $result) {
if ($result instanceof \Throwable) {
logger()->warning("Message {$key} failed: {$result->getMessage()}");
continue;
}
// $result is a SentMessageResponse
}Controlling concurrency
Pass an int for a fixed cap, or any callable that receives the number of pending requests and returns the concurrency to use at that point:
Africastalking::sms()->pool($messages, concurrency: fn(int $pendingRequests) => min($pendingRequests, 10));Bulk and Premium messages cannot be mixed in one pool
A single pool() call must contain either all bulk SMS messages or all premium SMS messages — Africa's Talking resolves each mode to a different API endpoint, so mixing them within one concurrent batch is unsupported. Mixing them throws an AfricastalkingException. Call pool() separately for each mode instead.