s-mailer / sdk
PHP SDK for the S-Mailer API — send messages, manage webhooks, and more
Requires
- php: >=8.1
- php-http/discovery: ^1.19
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/http-message: ^1.1 || ^2.0
Requires (Dev)
- guzzlehttp/guzzle: ^7.8
- phpstan/phpstan: ^1.11
- phpunit/phpunit: ^10.5
Suggests
- guzzlehttp/guzzle: A PSR-18 HTTP client and PSR-17 factories (recommended default transport)
README
PHP SDK for the S-Mailer API — send messages across SMS, email, WhatsApp and more, list sent/received messages, check your balance, and verify webhooks.
Status: pre-1.0. All resource methods are implemented; the API may still change before
1.0.0.
Requirements
- PHP 8.1+
- A PSR-18 HTTP client and PSR-17 factories (e.g. Guzzle). They are auto-discovered via
php-http/discovery; you can also inject your own (see Configuration).
Installation
composer require s-mailer/sdk
If you don't already have a PSR-18 client installed:
composer require guzzlehttp/guzzle
Authentication
Every request authenticates with your X-Client-ID / X-Client-Secret pair. Find them in your S-Mailer dashboard.
$mailer = new \SMailer\Client('CLT-XXXXXXXX', 'your-api-secret');
Usage
Send a message
$result = $mailer->messages->send([ 'sender' => 'sms-co1-xxxxxxxx', 'recipients' => [ '+258841234567', ['phone' => '+258842000001', 'data' => ['name' => 'Alice']], ], 'content_template' => 'Hi ${name}, your code is ${code}.', // 'channel' => 'sms', 'provider' => 'sms-co1', // alternatives to `sender` // 'campaign_id' => 'promo-2026', // 'webhook_url' => 'https://your-app.com/webhooks/status', ]); // => ['message_id' => ..., 'external_id' => ..., 'total_cost_tokens' => ...]
List and fetch messages
$sent = $mailer->messages->listOutbound(['status' => 'delivered', 'channel' => 'sms', 'limit' => 50]); $received = $mailer->messages->listInbound(['limit' => 50]); $one = $mailer->messages->get($messageId); // outbound or inbound; see `direction`
Retrigger / delete
$mailer->messages->retrigger($inboundId); // unlock + re-deliver a locked (unpaid) inbound $mailer->messages->delete($messageId); // delete an outbound or paid inbound message
Balance, profile, channels
$tokens = $mailer->balance->get(); // int $profile = $mailer->balance->profile(); // includes webhook_signing_key $channels = $mailer->channels->list(); // public senders + your private ones (authenticated)
Configuration
Pass a third $options array to tune the client:
$mailer = new \SMailer\Client('CLT-XXXXXXXX', 'your-api-secret', [ 'baseUrl' => 'https://api.mailer.smartek.co.mz', // override the API host 'maxRetries' => 2, // retry 429/5xx/network (default 2) // Inject your own PSR-18 client / PSR-17 factories instead of auto-discovery: // 'httpClient' => $psr18Client, // 'requestFactory' => $psr17RequestFactory, // 'streamFactory' => $psr17StreamFactory, ]);
Retries use an exponential backoff and honour a Retry-After header on 429.
Webhooks
S-Mailer signs every webhook it sends with X-Mailer-Signature: hex(HMAC-SHA256(webhook_signing_key, raw_body)). Your webhook_signing_key comes from GET /api/v1/client ($mailer->balance->profile()). Verify over the raw request body, before decoding it.
$raw = file_get_contents('php://input'); $sig = $_SERVER['HTTP_X_MAILER_SIGNATURE'] ?? ''; $event = (new \SMailer\Webhook())->parse($raw, $sig, $signingKey); // throws on bad signature if (($event['payment_required'] ?? false) === false) { // handle inbound message / status update }
Two payload shapes are delivered, distinguished by their fields:
- Inbound:
{ id, from, to, channel, body, received_at, sender_id, client_id, payment_required } - Status:
{ message_id, recipient, status: { old, current }, error_code, timestamp }
Errors
Non-2xx responses raise a \SMailer\Exception\SMailerException subclass carrying the API error code (->errorCode) and HTTP status (->statusCode):
| Status | Exception (\SMailer\Exception\…) |
error |
|---|---|---|
| 401 | AuthenticationException |
AUTHENTICATION_FAILED |
| 403 | ForbiddenException |
FORBIDDEN |
| 400 | BadRequestException / ValidationException |
BAD_REQUEST / VALIDATION_ERROR |
| 402 | InsufficientTokensException |
INSUFFICIENT_TOKENS |
| 404 | NotFoundException |
NOT_FOUND |
| 429 | RateLimitException (->retryAfter) |
RATE_LIMIT_EXCEEDED |
| 5xx | ServerException |
INTERNAL_SERVER_ERROR |
Network/transport failures raise NetworkException.
use SMailer\Exception\InsufficientTokensException; use SMailer\Exception\RateLimitException; try { $mailer->messages->send([/* ... */]); } catch (InsufficientTokensException $e) { // top up the account } catch (RateLimitException $e) { sleep($e->retryAfter ?? 1); }
License
MIT — see LICENSE.