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

Octopush Notifier Laravel Package

symfony/octopush-notifier

Symfony Notifier integration for Octopush, letting your app send SMS through Octopush's gateway. Configure the DSN with your login, API key, sender and SMS type (XXX low-cost, FR premium, or WWW world) to start delivering text messages.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: it is not a Laravel package. It's a Symfony Notifier transport (one class pair: OctopushTransport + OctopushTransportFactory) that maps a Symfony\Component\Notifier\Message\SmsMessage onto an Octopush SMS campaign HTTP call. There is no ServiceProvider, no config/octopush.php, no Laravel notification channel, and no inbound webhook handling. Everything you get out of the box is outbound SMS only — despite what some internal write-ups say about "push notifications".

Pull it in alongside the Notifier component and the HTTP client it needs:

composer require symfony/notifier symfony/http-client symfony/octopush-notifier

Pin to the same minor as the Symfony components Laravel already ships (Laravel 12 ≈ ^7.2). Don't let Composer jump to ^8.0 — that release requires PHP 8.4. symfony/http-client coexists happily with Guzzle/Laravel's Http facade; there is no conflict to worry about.

Where to look first, in order:

  1. vendor/symfony/octopush-notifier/OctopushTransportFactory.php — the DSN parser. Confirms the exact option names, which are required, and whether the host must literally be default.
  2. vendor/symfony/octopush-notifier/OctopushTransport.php — doSend(): the endpoint, the JSON payload, and whether/how it sets a message ID or surfaces Octopush-level errors.
  3. vendor/symfony/notifier/Transport.php — fromDsn() constructs the factory for you from any scheme it knows.

First use case — send an SMS from a queue job, no Notifier plumbing:

// config/services.php
'octopush' => [
    'dsn' => env('OCTOPUSH_DSN'), // octopush://EMAIL:APIKEY@default?from=MyApp&type=XXX
],
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\Notifier\Message\SmsMessage;
use Symfony\Component\Notifier\Transport;

$transport = Transport::fromDsn(
    config('services.octopush.dsn'),
    null,                                        // event dispatcher is optional
    HttpClient::create(['timeout' => 5, 'max_duration' => 10]),
);

$transport->send(new SmsMessage('33612345678', 'Your code is 4821'));

That is genuinely all this package does. The value you get is DSN parsing, credential validation, and consistent exception types — not a lot, but not nothing, and it's MIT and versioned in lockstep with Symfony.

Implementation Patterns

Bind the transport as a singleton and write a Laravel channel

The clean integration is not to boot the whole Notifier service; use the transport as a typed HTTP client and keep Laravel's notification system as the front door.

// app/Providers/OctopushServiceProvider.php
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\Notifier\Transport;
use Symfony\Component\Notifier\Transport\TransportInterface;

public function register(): void
{
    $this->app->singleton(TransportInterface::class, fn () => Transport::fromDsn(
        config('services.octopush.dsn'),
        null,
        HttpClient::create(['timeout' => 5, 'max_duration' => 10]),
    ));
}
// app/Notifications/Channels/OctopushChannel.php
class OctopushChannel
{
    public function __construct(private TransportInterface $transport) {}

    public function send(object $notifiable, Notification $notification): void
    {
        $phone = $notifiable->routeNotificationFor('octopush', $notification);

        if (! $phone || ! method_exists($notification, 'toOctopush')) {
            return;
        }

        $this->transport->send(new SmsMessage($phone, $notification->toOctopush($notifiable)));
    }
}
class AlertNotification extends Notification implements ShouldQueue
{
    public function via(object $notifiable): array { return ['octopush']; }

    public function toOctopush(object $notifiable): string
    {
        return "Server {$this->host} is down.";
    }
}

ShouldQueue matters: the transport does a blocking HTTP call, and Octopush latency spikes shouldn't be in your request path.

Let Laravel's queue absorb the failure modes

SMS costs money per unit, so duplicate sends are worse than a lost send. Use job middleware rather than retry-everything defaults:

public function middleware(): array
{
    return [(new RateLimited('octopush'))->dontRelease()];
}

Define the octopush limiter in AppServiceProvider::boot() with a limit that matches your Octopush contract. Combine with backoff() of 10–30s and tries = 3; the transport throws Symfony\Component\Notifier\Exception\TransportException on non-2xx and TransportExceptionInterface on network failure, both of which Laravel's queue will retry correctly.

Failover across providers

Because you're talking to Transport, not to a Laravel driver, you can use Symfony's Transports wrapper to get "try A, fall back to B":

use Symfony\Component\Notifier\Transport\Transports;

$transports = Transports::fromStrings([
    config('services.octopush.dsn'),
    config('services.twilio.dsn'), // or any other Notifier SMS bridge
], null, HttpClient::create());

Keep this behind your own interface so a future provider swap doesn't leak into notification classes.

Testing without burning credits

MockHttpClient from symfony/http-client is the whole toolkit:

$transport = Transport::fromDsn(
    'octopush://login:key@default?from=MyApp&type=XXX',
    null,
    new MockHttpClient(new MockResponse('{"ticket_id":"abc"}', ['http_code' => 200])),
);

$transport->send(new SmsMessage('33612345678', 'hello'));

Bind the mock into the container in TestCase::setUp() and your channel tests need no HTTP fakes at all. Assert on the recorded request body via MockHttpClient::getRequestsCount() / a custom response factory closure if you want to verify sms_text and sms_sender.

Gotchas and Tips

The DSN is the entire configuration surface. from (sender) and type (XXX low-cost, FR premium France, WWW world) are read from the query string and are fixed per transport instance. There is no OctopushSmsOptions class, so you cannot change sender or routing type per message. If you need "premium for FR, low-cost elsewhere", register two transports with separate DSNs and pick between them in your channel.

type is a billing decision, not a formatting one. WWW is the expensive international route; leaving it as a "safe default" quietly multiplies your SMS unit cost. FR requires sender ID pre-registration with Octopush and will be rejected otherwise. Verify the sender you pass in from is actually approved for the route you selected before going live.

Percent-encode credentials. The API key is a DSN password; keys containing @, :, / or % will break Dsn::fromString() and you'll get an opaque UnsupportedSchemeException/IncompleteDsnException rather than "bad password". Rotate to a key without reserved characters, or encode it and assert the parsed DSN in a test.

Never call env() for the DSN outside config/. Standard Laravel rule, but it bites harder here: config:cache returns null and Transport::fromDsn(null) throws deep inside Symfony with an unhelpful message.

Check whether Octopush errors come back as HTTP 200. The transport's contract is "non-2xx → TransportException". Octopush's API has historically returned 200 with an error payload for some validation failures (bad sender, no credits), which the bridge will treat as success. Open OctopushTransport::doSend() and confirm what it does with the response body. If it doesn't inspect it, log $response->toArray(false) yourself in a wrapper, or you'll discover failed sends only from customer complaints. Same for SentMessage::getMessageId() — don't assume it's populated; don't build idempotency on it without checking.

Phone numbers are passed through untouched. No E.164 normalisation happens in the bridge. Normalise to international format (33612345678, no spaces or + differences) in your notification class, or Octopush will silently reject or misroute. Also remember non-GSM characters (emoji, accents outside GSM-7) flip the message to UCS-2 and roughly halve the characters per billable SMS — truncate deliberately.

The DSN secret will land in logs and exception traces if you dump the config. Symfony tags the factory's createTransport() with #[\SensitiveParameter] in recent versions, but your own Log::debug(config('services.octopush')) won't be so careful. Log the transport's __toString() (which yields octopush://default?from=...&type=...) if you need debug output.

Delivery receipts are entirely on you. The bridge is outbound-only; there's no request parser, no signature validation, no event. If you need per-message delivery status, add your own POST /webhooks/octopush route, store the Octopush ticket/campaign ID against your notification record at send time, and correlate.

Extension point if the payload doesn't fit: don't fork the bridge. Either (a) keep using it and post-process nothing — accept the fixed payload — or (b) drop to symfony/http-client directly in an OctopushClient of your own, which is ~30 lines and gives you full control over sender, type, callback URL and error parsing. Given how thin the bridge is, option (b) is a legitimate choice for teams that need per-message options; the bridge's real worth is standardised exceptions and the failover story via Transports.

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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin