S-Mailer SDKs

Official SDKs for integrating with the S-Mailer API — send messages across SMS, WhatsApp, and email, list activity, check your balance, and verify webhooks.

Installation

composer require s-mailer/sdk

The SDK talks to any PSR-18 HTTP client. If you don't already have one installed:

composer require guzzlehttp/guzzle

Send a message

<?php

$mailer = new \SMailer\Client('CLT-XXXXXXXX', 'your-api-secret');

$result = $mailer->messages->send([
    'sender'    => 'sms-co1-xxxxxxxx',
    'recipient' => '+258841234567',
    'content'   => 'Hello from S-Mailer!',
]);

echo $result['message_id'];

Send to many recipients with per-recipient template data:

$mailer->messages->send([
    'sender'           => 'sms-co1-xxxxxxxx',
    'recipients'       => [
        '+258841234567',
        ['phone' => '+258842000001', 'data' => ['name' => 'Alice']],
    ],
    'content_template' => 'Hi ${name}, your code is ${code}.',
]);

List, fetch, balance, channels

$sent     = $mailer->messages->listOutbound(['status' => 'delivered', 'limit' => 50]);
$received = $mailer->messages->listInbound(['limit' => 50]);
$one      = $mailer->messages->get($messageId);   // outbound or inbound (see `direction`)

$balance  = $mailer->balance->get();       // int — current token balance
$profile  = $mailer->balance->profile();   // incl. webhook_signing_key
$channels = $mailer->channels->list();     // channels → providers → senders

Retrigger & delete

$mailer->messages->retrigger($inboundId); // charge + re-deliver a locked (unpaid) inbound
$mailer->messages->delete($messageId);    // delete an outbound or paid inbound

WhatsApp advanced (media, buttons, location, contact, reactions)

WhatsApp senders only. Text is billed at the outbound cost; media, buttons, location, contact, and reactions at the advanced cost.

$sender = 'whatsapp-co1-xxxxxxxx';
$to     = '+258841234567';

$mailer->whatsapp->sendText($sender, $to, 'Hello 👋');

// Host media anywhere publicly reachable, then pass the URL.
$mailer->whatsapp->sendMedia($sender, $to, [
    'kind' => 'image', 'url' => 'https://cdn.example.com/promo.jpg', 'filename' => 'promo.jpg',
], caption: 'Our new catalogue');

$res = $mailer->whatsapp->sendButtons($sender, $to, 'Confirm your appointment?', [
    ['id' => 'yes', 'title' => 'Yes'],
    ['id' => 'no',  'title' => 'No'],
], footer: 'S-Mailer');

// Location pin.
$mailer->whatsapp->sendLocation($sender, $to, -25.9655, 32.5832, title: 'Smartek HQ');

// Contact card (phone is E.164; becomes the card's tap-to-chat number).
$mailer->whatsapp->sendContact($sender, $to, [
    'name' => 'John Doe', 'phone' => '+258841112222', 'organization' => 'Acme Lda',
]);

// React to a message (outbound or inbound); '' removes the reaction.
$mailer->whatsapp->react($sender, $res['message_id'], '👍');

Verify webhooks

S-Mailer signs every webhook with X-Mailer-Signature: hex(HMAC-SHA256(webhook_signing_key, raw_body)). Your webhook_signing_key comes from $mailer->balance->profile(). Verify over the raw 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 the inbound message / status update
}

Errors

Non-2xx responses raise a \SMailer\Exception\SMailerException subclass carrying ->errorCode and ->statusCode:

StatusExceptionerror
401AuthenticationExceptionAUTHENTICATION_FAILED
403ForbiddenExceptionFORBIDDEN
400BadRequestException / ValidationExceptionBAD_REQUEST
402InsufficientTokensExceptionINSUFFICIENT_TOKENS
404NotFoundExceptionNOT_FOUND
429RateLimitException (->retryAfter)RATE_LIMIT_EXCEEDED

Reference

Full method reference and options live in the repository README.

github.com/s-mailer/sdk-php

Installation

npm install @s-mailer/sdk

Ships ESM + CommonJS with TypeScript types. Node 18+ (uses the global fetch).

Send a message

import { SMailer } from '@s-mailer/sdk';

const mailer = new SMailer({
  clientId: 'CLT-XXXXXXXX',
  clientSecret: 'your-api-secret',
});

const res = await mailer.messages.send({
  sender: 'sms-co1-xxxxxxxx',
  recipient: '+258841234567',
  content: 'Hello from S-Mailer!',
});

console.log(res.message_id);

Send to many recipients with per-recipient template data:

await mailer.messages.send({
  sender: 'sms-co1-xxxxxxxx',
  recipients: [
    '+258841234567',
    { phone: '+258842000001', data: { name: 'Alice' } },
  ],
  contentTemplate: 'Hi ${name}, your code is ${code}.',
});

List, fetch, balance, channels

const sent     = await mailer.messages.listOutbound({ status: 'delivered', limit: 50 });
const received = await mailer.messages.listInbound({ limit: 50 });
const one      = await mailer.messages.get(messageId); // outbound or inbound (see `direction`)

const balance  = await mailer.balance.get();     // number — current token balance
const profile  = await mailer.balance.profile(); // incl. webhook_signing_key
const channels = await mailer.channels.list();   // channels → providers → senders

Retrigger & delete

await mailer.messages.retrigger(inboundId); // charge + re-deliver a locked (unpaid) inbound
await mailer.messages.delete(messageId);    // delete an outbound or paid inbound

WhatsApp advanced (media, buttons, location, contact, reactions)

WhatsApp senders only. Text is billed at the outbound cost; media, buttons, location, contact, and reactions at the advanced cost.

const sender = 'whatsapp-co1-xxxxxxxx';
const to = '+258841234567';

await mailer.whatsapp.sendText(sender, to, 'Hello 👋');

// Host media anywhere publicly reachable, then pass the URL.
await mailer.whatsapp.sendMedia(sender, to,
  { kind: 'image', url: 'https://cdn.example.com/promo.jpg', filename: 'promo.jpg' },
  'Our new catalogue');

const res = await mailer.whatsapp.sendButtons(sender, to, 'Confirm your appointment?', [
  { id: 'yes', title: 'Yes' },
  { id: 'no',  title: 'No' },
], 'S-Mailer');

// Location pin.
await mailer.whatsapp.sendLocation(sender, to, -25.9655, 32.5832, 'Smartek HQ');

// Contact card (phone is E.164; becomes the card's tap-to-chat number).
await mailer.whatsapp.sendContact(sender, to,
  { name: 'John Doe', phone: '+258841112222', organization: 'Acme Lda' });

// React to a message (outbound or inbound); '' removes the reaction.
await mailer.whatsapp.react(sender, res.message_id, '👍');

Verify webhooks

S-Mailer signs every webhook with X-Mailer-Signature: hex(HMAC-SHA256(webhook_signing_key, raw_body)). Your webhook_signing_key comes from mailer.balance.profile(). Verify over the raw body, before parsing it.

import { webhooks } from '@s-mailer/sdk';

// Express — raw body required
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const raw = req.body.toString('utf8');
  const sig = req.header('X-Mailer-Signature') ?? '';

  const event = webhooks.parse(raw, sig, signingKey); // throws on bad signature
  if (event.type === 'inbound' && !event.payment_required) {
    // handle the inbound message
  } else if (event.type === 'status') {
    // handle the delivery status update
  }
  res.sendStatus(200);
});

Errors

Non-2xx responses reject with a typed error extending SMailerError, carrying errorCode and statusCode:

StatusError classerror
401AuthenticationErrorAUTHENTICATION_FAILED
403ForbiddenErrorFORBIDDEN
400BadRequestError / ValidationErrorBAD_REQUEST
402InsufficientTokensErrorINSUFFICIENT_TOKENS
404NotFoundErrorNOT_FOUND
429RateLimitError (retryAfter)RATE_LIMIT_EXCEEDED

Reference

Full method reference and options live in the repository README.

github.com/s-mailer/sdk-node