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

Pushbullet Laravel Package

ivkos/pushbullet

PHP client library for the Pushbullet API that lets apps send push notifications, links, files and SMS to phones, tablets and desktops, and manage devices, contacts and channels. A simple, framework-agnostic wrapper around Pushbullet's REST endpoints, installable via Composer.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package like any Composer dependency, then grab an access token from pushbullet.com → Settings → Account → Access Tokens:

composer require ivkos/pushbullet
# .env
PUSHBULLET_TOKEN=o.xxxxxxxxxxxxxxxxxxxxxxxxxxxx
// config/services.php
'pushbullet' => [
    'token' => env('PUSHBULLET_TOKEN'),
    'device' => env('PUSHBULLET_DEVICE'), // optional default target iden
],

The first useful thing you can do is a smoke test from php artisan tinker:

use Pushbullet\Pushbullet;

$pb = new Pushbullet(config('services.pushbullet.token'));
$pb->pushNote('Hello from Laravel', 'Deployed ' . config('app.env') . ' at ' . now());

That call proves three things at once: token validity, outbound HTTPS/curl from your host, and that the Pushbullet account can receive pushes. From there, discover the iden of the device you actually want to target — you rarely want "all devices" in production:

foreach ($pb->devices() as $device) {
    // dump($device->iden, $device->nickname, $device->active);
}

The library has no Laravel auto-discovery, service provider, or facade. That's not a defect, it's just the deal: it's a plain SDK. Where to look first in the repo: src/ for the Pushbullet class and the Device / Push / Channel value objects, the Exceptions/ folder to see what can be thrown, and the README for the full push-type list (pushNote, pushLink, pushFile, pushSMS, list management on devices/channels/contacts).

First real use case for a Laravel app: a queued "ops alert" — failed queue job, failed scheduled command, or an exception in the handler that should ping your phone rather than your inbox. Register it as a singleton so the client (and its curl handle behavior) is reused within a request:

// app/Providers/AppServiceProvider.php
$this->app->singleton(\Pushbullet\Pushbullet::class, fn () =>
    new \Pushbullet\Pushbullet(config('services.pushbullet.token'))
);

Implementation Patterns

Wrap it, don't sprinkle it. Because there's no transport abstraction, the single most valuable pattern is a thin adapter interface so the SDK never appears in your domain code:

namespace App\Notifications;

interface MobileNotifier
{
    public function note(string $title, string $body, ?string $deviceIden = null): void;
    public function link(string $url, string $title, string $body, ?string $deviceIden = null): void;
}
namespace App\Notifications;

use Pushbullet\Pushbullet;
use Pushbullet\Exceptions\PushbulletException;

class PushbulletNotifier implements MobileNotifier
{
    public function __construct(private Pushbullet $client, private ?string $defaultDevice = null) {}

    public function note(string $title, string $body, ?string $deviceIden = null): void
    {
        $target = $deviceIden ?? $this->defaultDevice;

        $target
            ? $this->client->device($target)->pushNote($title, $body)
            : $this->client->pushNote($title, $body);
    }

    public function link(string $url, string $title, string $body, ?string $deviceIden = null): void
    {
        $target = $deviceIden ?? $this->defaultDevice;

        $target
            ? $this->client->device($target)->pushLink($url, $title, $body)
            : $this->client->pushLink($url, $title, $body);
    }
}

Bind the interface, and in testing bind a FakeNotifier that collects calls. This is the only reliable way to test code that sends pushes — see Gotchas.

Always send from a queued job. Pushbullet is a third-party HTTPS round trip; a slow or hanging connection must never sit in a web request or in a Schedule::run() critical path.

class SendMobileAlert implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries = 3;
    public int $backoff = 30;
    public int $timeout = 15;

    public function __construct(
        public string $title,
        public string $body,
        public ?string $deviceIden = null,
        public bool $critical = false,
    ) {}

    public function handle(MobileNotifier $notifier): void
    {
        $notifier->note($this->title, $this->body, $this->deviceIden);
    }

    public function failed(\Throwable $e): void
    {
        // The push channel failed; fall back to mail/Slack rather than looping.
        Log::error('Pushbullet delivery failed', ['title' => $this->title, 'error' => $e->getMessage()]);
    }
}

Fan-out by intent, not by "all devices". The account-level $pb->pushNote(...) broadcasts to every active device, which is right for "prod is down" and wrong for "your report is ready". Use $pb->device($iden)->pushNote(...) for personal/team-specific noise, and a channel when you have subscribers:

$pb->channel('deploys')->pushNote('v2.14.0 shipped', 'Rolled out to production.');

Hook it into the exception handler and the scheduler. A single report() override with a RateLimiter guard is the highest-signal-per-line integration you'll write — you get paged for real 5xx bursts without a single push per exception.

public function report(Throwable $e): void
{
    parent::report($e);

    if ($this->shouldAlert($e) && RateLimiter::attempt('pb-alert', 5, fn () => true, 300)) {
        SendMobileAlert::dispatch(
            '[' . config('app.env') . '] ' . class_basename($e),
            Str::limit($e->getMessage(), 300)
        );
    }
}

Reading side. If you want an inbox view, $pb->pushes() returns recent pushes (cursor-paginated via modified_after/cursor — loop manually), and $pb->deletePush($iden) cleans up so you don't accumulate thousands of records. If you keep push idens in your own table, you can dismiss programmatically later.

Gotchas and Tips

Http::fake() will not intercept this library. It talks to the API via its own curl code, not Guzzle or Laravel's HTTP client. Any test that relies on Http::fake() around a Pushbullet call will hit the real network. The adapter interface above is not optional style advice — it's the testing strategy.

Catch Pushbullet\Exceptions\PushbulletException. Every non-2xx response surfaces as an exception, and the common codes are diagnostically distinct: 401 = bad/revoked token, 400 = the push type isn't supported for that target (SMS to a non-Android or SMS-permission-less device is the classic), 429 = rate limited. Wrap calls and translate; don't let a notification failure 500 a user-facing request.

Rate limits are per token and lower than you'd guess. Do not push from inside loops over collections. Aggregate into one message, or dispatch jobs with backoff. A foreach ($users as $u) { $pb->pushNote(...) } will get throttled quickly.

Idens over nicknames. Device nicknames get edited by users ("Pixel 8" → "work phone"), idens don't. Resolve idens once, put them in config/.env, and log the nickname alongside if you need humans to understand logs.

No configurable timeouts or base URL. The endpoint is hard-coded and curl options aren't exposed. Consequences: a hung connection eats your worker's timeout, and you cannot point it at a mock server. Mitigate with queued jobs (job timeout + retry_after), and if you truly need transport control, subclass the client and override the protected request method, or drop the wrapper and call POST /v2/pushes with Laravel's HTTP client — the API surface you actually use is tiny.

Token hygiene. Never dd(config('services.pushbullet.token')) in a shared terminal, and don't put the token in an exception context array. It goes in the Access-Token header; keep it out of logs, Sentry breadcrumbs, and support tickets.

It's unmaintained since 2016. Pin the exact version in composer.json. If Composer refuses to install on a recent PHP due to the php constraint, the code itself is plain curl and typically fine — widening the constraint in a fork is usually the whole fix, and worth it versus abandoning the wrapper. Keep the adapter boundary so a future swap to raw HTTP is a one-file change.

File pushes need real paths. pushFile uploads multipart; pass an absolute, readable path (storage_path(...)), not a Storage disk key. Large files risk timeouts you can't tune — prefer pushLink to a signed URL for anything sizable.

Timestamps are float epoch seconds, not strings or DateTime. Convert explicitly before storing, or you'll get surprising Carbon behavior.

Extension points are thin by design: value objects (Device, Push, Channel) for reads, subclassing for transport tweaks, and the adapter for everything else. Lean on the adapter; treat the SDK as an implementation detail you could replace in an afternoon.

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.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource