Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Clockworksms Laravel Package

mediaburst/clockworksms

A PHP client for the Clockwork SMS API that lets you send single or bulk text messages, check credits, and handle delivery reports. Easy to integrate into Laravel apps for notifications, alerts, and two-factor codes.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a thin, framework-agnostic wrapper around the Clockwork (Mediaburst) SMS HTTP API. There is no Laravel package, service provider, facade, or config file — you get two classes:

  • Mediaburst\Clockwork\Clockwork — the HTTP client (auth + transport)
  • Mediaburst\Clockwork\Message — the message value object you build and hand to the client

Install

composer require mediaburst/clockworksms

Read the README first — it is short and is the only real documentation you get. Then open src/Clockwork.php and src/Message.php in vendor/mediaburst/clockworksms/; there are only a handful of public methods and skimming them answers most "does it support X?" questions. If the namespace in the README differs from the one you expect, check composer.json's autoload map — the package was renamed at one point.

Get a key

Sign up at clockwork.io (or use the Mediaburst dashboard) and go to Settings → API keys. The key is what you pass to the constructor.

First send

use Mediaburst\Clockwork\Clockwork;
use Mediaburst\Clockwork\Message;

$clockwork = new Clockwork(config('services.clockwork.key'));

$message = new Message('+441234567890', 'Your appointment is confirmed.');
$message->setFrom('MYBRAND');          // 11 alphanumeric chars or a long number
$message->setReference('appt-88213');  // echoed back in delivery reports

$result = $clockwork->send($message);

if ($result['success']) {
    $messageId = $result['id'];
} else {
    // $result['error'] / $result['errno']
}

That is the entire happy path. Everything else is either message options (setFrom, setReference, setTtr, setInvalidCharacterAction, setConcat, setFlash) or the reporting endpoints (getBalance, getDeliveryStatus, getMessages).

First use case: transactional notifications

The realistic first thing you will build is "fire-and-forget SMS from a queued job" — order confirmation, OTP, appointment reminder. Keep the send inside a queued job and treat the returned id as a correlation handle for the delivery report.

Implementation Patterns

Wrap it in a service class, always

Do not scatter new Clockwork(...) through your app. The package has no DI hook, but a one-line binding is enough:

// App\Services\ClockworkSms
class ClockworkSms
{
    public function __construct(private Clockwork $client) {}

    public function send(string $to, string $text, array $opts = []): array
    {
        $message = new Message($to, $text);

        if (isset($opts['from']))      $message->setFrom($opts['from']);
        if (isset($opts['reference'])) $message->setReference($opts['reference']);
        if (isset($opts['ttr']))       $message->setTtr($opts['ttr']);

        return $this->client->send($message);
    }
}
// AppServiceProvider
$this->app->singleton(Clockwork::class, fn () =>
    new Clockwork(config('services.clockwork.key'))
);
$this->app->singleton(ClockworkSms::class);

The client is stateless apart from the key, so a singleton is safe and keeps a single HTTP connection pool.

Send from a queued job, never inline

SMS providers have variable latency and the wrapper does a blocking Guzzle call. A job with $tries = 3 and $backoff = 30 gives you natural retry semantics:

public function handle(ClockworkSms $sms)
{
    $result = $sms->send($this->to, $this->text, ['reference' => $this->order->id]);

    if (! $result['success']) {
        // 401/402-class errno → don't retry, it's your fault (bad key, no credit)
        if (in_array($result['errno'] ?? 0, [9, 10, 25])) {
            $this->fail(new \RuntimeException($result['error']));
        }
        $this->release(30);
    }
}

Never retry on success = false without inspecting errno — bad key or zero balance will burn your whole queue retry budget.

Bulk sends

send() accepts an array of Message objects too, and fans them out in one HTTP round trip:

$messages = collect($subscribers)->map(
    fn ($s) => (new Message($s->phone, $body))->setReference($campaign->id)
)->all();

$results = $clockwork->send($messages);

Each element of $results has its own success/id, so match by index, not by phone number (duplicates are legal). Bulk reduces latency but not cost — you are still charged per segment.

Correlating delivery reports

Keep the id returned from send() and use setReference() to carry your own identifier alongside it. When a delivery report webhook or a manual getDeliveryStatus($id) call arrives, you can find the row by either. Store both columns:

SmsLog::create([
    'provider_id' => $result['id'],
    'reference'   => $order->id,
    'status'      => 'sent',
]);

getMessages($start, $end) accepts dates and returns recent message history — use it for reconciliation jobs rather than as your primary record.

Tracking cost

Call getBalance() on a scheduled command daily and alert when it drops below your threshold. Credits are consumed per 160-character GSM segment, so a 320-char message costs 2 credits. Log strlen($text) alongside the send so you can reconstruct cost later.

Gotchas and Tips

It is a 2017 package — dependency archaeology required

The last tag is from November 2017. That predates Guzzle 7 and PSR-18. Check composer.lock immediately after install: on a modern Laravel you will often hit a constraint conflict on guzzlehttp/guzzle. The pragmatic fixes, in order of preference:

  1. Fork the package and bump the Guzzle constraint (the wrapper uses a tiny slice of the client, so a version bump is usually a one-line composer.json edit).
  2. Add a replace / provide shim in your root composer.json.
  3. Do not use --ignore-platform-reqs as a first resort; the failure is usually an interface change, not just a version number.

Do not assume PHP 8 compatibility. Run the send path once in CI against a real key (or a fake HTTP client) before trusting it in production.

There is no fake / no testing hook

The client is constructed with a key and calls out immediately. For tests, bind Clockwork to a MockHandler-backed Guzzle client if the class exposes injection, otherwise wrap sends in your ClockworkSms class and swap that binding in tests. Do not let real HTTP leave your test suite.

errno is more useful than the string

The API returns errno (an integer) alongside error. Branch on errno, log the string. The important classes:

  • auth / billing (bad key, no credit) — permanent failures, do not retry
  • recipient (invalid number, blacklisted) — permanent per-message, fail the job
  • provider / temporary (network, rate limit) — retry with backoff

Mapping table lives in the Clockwork API docs, not in the package.

GSM vs Unicode segments

The default character set handling is GSM-7. If you send anything with emoji, curly quotes, or accented characters you will silently pay for UCS-2 segments (70 chars per credit) unless you set how invalid characters are handled. For OTPs and alerts, force ASCII at the source — do not rely on the library to transliterate.

$message->setInvalidCharacterAction(Message::INVALID_CHARACTER_REPLACE);

Use INVALID_CHARACTER_ERROR in staging to catch stray Unicode before it hits the bill.

Sender ID rules vary by destination

Alphanumeric from values (e.g. MYBRAND) work in the UK and most of Europe but are dropped or rejected for US/Canada and some Asian routes. If you are international, either use a provisioned long code or accept that from may be silently ignored and the message arrives from a shared short code.

The to number needs a country code

+441234567890 — no leading zero after the country code, no spaces, no brackets. The wrapper will happily send garbage and return success: false with a low-level error; you will waste time debugging "why is my error empty" only to find the number was 01234 567 890.

No connection pooling control, no timeouts exposed

The wrapper constructs its own HTTP client internally (or accepts one, depending on version). You almost certainly cannot set a per-request timeout from the public API. If a provider outage is a business risk, wrap the Clockwork singleton in a decorator that enforces a timeout via a custom Guzzle handler, or catch ClockworkException / GuzzleException at the job boundary and release with backoff.

Extension points you do not have

There is no middleware, no event, no response object — you get associative arrays. If you need richer behaviour (structured exceptions, metrics, tracing), build it in your ClockworkSms wrapper, not in the package. Treat the vendor folder as read-only and keep the adapter thin so you can swap to the raw HTTP endpoint, or to a maintained alternative like vonage/client or twilio/sdk, without touching call sites.

Consider whether you should use it at all

45 stars, zero dependents, no release since 2017. If this is a greenfield project, look at Clockwork's raw REST API — it is a simple JSON POST and you can implement the four calls you actually need in ~80 lines with Http::withHeaders(...)->post(...). That gives you modern Guzzle, timeouts, retries, Http::fake(), and no dependency archaeology. Reserve this package for codebases that already use it and just need maintenance.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle