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

Prowl Bundle Laravel Package

micronax/prowl-bundle

Micronax ProwlBundle integrates Prowl push notifications into Symfony applications, offering services and configuration to send alerts to iOS devices via the Prowl API for lightweight monitoring and event notifications.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check before you type a command: micronax/prowl-bundle is a Symfony2 bundle, not a Laravel package. It pins symfony/framework-bundle to the 2.x line, ships an Acme\...Bundle class registered via AppKernel::registerBundles(), and exposes services through Symfony's DI container. composer require micronax/prowl-bundle inside a modern Laravel app will fail dependency resolution — do not try to force it with --ignore-platform-reqs.

What you actually consume is the library it wraps: xenji/prowlphp. Treat the bundle repository as a specification for config keys, service wiring, and API call shape.

composer require xenji/prowlphp
// config/services.php — mirror the bundle's Configuration.php tree
'prowl' => [
    'api_key'      => env('PROWL_API_KEY'),
    'provider_key' => env('PROWL_PROVIDER_KEY'), // optional, for volume > user key
    'application'  => env('APP_NAME', 'Laravel'),
    'timeout'      => 5,
],
// app/Providers/ProwlServiceProvider.php
public function register(): void
{
    $this->app->singleton(\Prowl\Prowl::class, function ($app) {
        $prowl = new \Prowl\Prowl();
        $prowl->setApiKey(config('services.prowl.api_key'));

        if ($key = config('services.prowl.provider_key')) {
            $prowl->setProviderKey($key);
        }

        return $prowl;
    });
}

Where to look first in the bundle repo:

  1. DependencyInjection/Configuration.php — the authoritative list of knobs (api key, provider key, application name, default priority).
  2. Resources/config/services.yml — the exact service wiring to mirror in your provider.
  3. The bundle's Service/ or wrapper class — a few dozen lines showing the push() call signature.

That's the whole surface area. Everything else is ProwlPHP itself.

First use case: ops alerting. A cron job fails, a deploy breaks, a contact form is submitted, and you want a push on your iPhone instead of an email you'll ignore. This is the classic Prowl use case and the fastest path to validating your API key, network egress, and priority handling end-to-end.

app(\Prowl\Prowl::class)->push(
    'Deploy failed on prod-web-2',
    'Deploys',
    1 // priority: -2 (very low) .. 2 (emergency)
);

Send one real notification manually before building anything. It confirms the key, the endpoint reachability, and that your priority maps correctly on the Prowl iOS client. Don't build abstractions until a bare send works.

Implementation Patterns

Wrap it, don't sprinkle it. Never call Prowl\Prowl directly from controllers, jobs, or listeners — the bundle would have given you a container-managed service; replicate that discipline with a thin interface so you can fake it in tests and swap providers later:

interface ProwlNotifier {
    public function notify(string $message, ?string $app = null, int $priority = 0, ?string $url = null): bool;
}

Bind the concrete ProwlPHP implementation in your service provider. All call sites depend on the interface. This is the single highest-value decision — Prowl is a legacy service and you will want to swap it without touching call sites.

Always queue. ProwlPHP does a synchronous HTTPS POST. Three seconds of latency inside a web request is a user-visible bug. Make a job:

final class SendProwlNotification implements ShouldQueue
{
    use InteractsWithQueue, Queueable;

    public function __construct(
        public string $message,
        public int $priority = 0,
        public ?string $url = null,
    ) {}

    public function handle(ProwlNotifier $prowl): void
    {
        $sent = $prowl->notify(
            $this->message,
            config('app.name'),
            $this->priority,
            $this->url,
        );

        if (! $sent) {
            // Prowl returns 200 with an error body for some failures —
            // the notifier should surface that as false, not silently pass.
            throw new RuntimeException('Prowl delivery rejected');
        }
    }

    public function backoff(): array { return [10, 60, 300]; }
    public function tries(): int { return 3; }
}

Use it as a Laravel Notification channel. This is the pattern that pays for itself day-to-day — you get via(), queueing via ShouldQueue, and consistent call sites for free:

class DeployFailed extends Notification implements ShouldQueue
{
    public function __construct(public string $reason) {}

    public function via($notifiable): array { return ['prowl']; }

    public function toProwl($notifiable): array
    {
        return [
            'message'  => "Deploy failed: {$this->reason}",
            'priority' => 2,
            'url'      => url('/deploys'),
        ];
    }
}

Channel class resolves the ProwlNotifier from the container, maps the payload, throws on non-success responses — identical to any built-in channel.

Priorities as policy. Encode them once in a small enum or constants rather than scattering -2..2 magic ints:

Priority Use Behavior on iOS
-2 Verbose / activity log No iOS alert
-1 Quiet info No alert
0 Normal Alert
1 High Alert + highlighted
2 Emergency Persistent until acknowledged

Map 2 only to paging-class events (prod down, data loss). Every dev who adopts Prowl goes through the same phase where they send everything at 2, then mute the app two weeks later.

Integration point that actually matters: a Notification::route('prowl', $key) for ad-hoc notifications to a specific user's Prowl key, and a config-driven default key for team-wide ops alerts. Keep both because the use cases are different.

Gotchas and Tips

HTTP 200 does not mean delivered. Prowl's API returns 200 OK with an error <error> body for several failure classes — invalid key, unapproved applications, rate limiting. ProwlPHP exposes this via the response object; a naive if ($http->status === 200) will silently drop notifications for weeks before you notice. Always check response->isSuccess() (or parse the body) and log/throw on failure.

The rate limit is real and per-key. Prowl enforces roughly 1,000 calls per provider key per hour, then returns 503-class errors. If you have a shared team key, one chatty tenant starves everyone. Mitigate with:

RateLimiter::for('prowl', fn ($job) => Limit::perHour(900)->by('shared'));

and route jobs through it in middleware(). Use provider_key (a separate credential Prowl offers for high-volume integrators) if you expect more.

API keys are effectively passwords. A leaked Prowl key lets anyone push arbitrary alerts to that user's device and burns their quota. Store in a secrets manager, not .env committed to a repo. Document rotation even if you never exercise it — there's no per-message revocation, only key rotation.

Don't retry 401/406/409. These are configuration states (bad key, app not approved, user not registered), not transient errors. Retrying forever just burns quota and buries the real problem. Classify the response and fail fast, alerting once:

// in your notifier's error mapping
$response->code === 401 => throw new InvalidArgumentException('Prowl key invalid');
$response->code === 503 => $this->release(300); // rate limit, back off

Curl timeout is not the HTTP client's timeout. ProwlPHP uses curl internally with its own timeout that may not respect your Laravel Http facade settings — it isn't using your HTTP client at all. Either wrap with a hard pcntl_alarm-free approach (queue worker timeout as safety net) or bypass ProwlPHP for anything latency-sensitive and call Http::asForm() directly:

Http::asForm()
    ->timeout(5)
    ->retry(2, 200, throw: false)
    ->post('https://api.prowlapp.com/publicapi/add', [
        'apikey'      => config('services.prowl.api_key'),
        'application' => config('app.name'),
        'event'       => $message,
        'description' => $description,
        'priority'    => $priority,
    ]);

You lose ~40 lines of ProwlPHP and gain control over retries, timeouts, and observability. In Laravel, this is usually the better trade.

Bundle extension points you'd have used in Symfony — emulate in Laravel. The bundle exposed the service via container config. In Laravel that maps to:

  • Publishable config (php artisan vendor:publish --tag=prowl-config) if you build your own package.
  • A ProwlFacade for ad-hoc sends from tinker or quick scripts — but keep facades out of domain code.
  • A NullProwlNotifier bound in testing environment so feature tests don't hit the network.
  • Notification::fake() compatible channel — implement toProwl() return shape consistently so assertSentOnDemand works.

Debugging checklist when notifications vanish:

  1. php artisan tinker → app(ProwlNotifier::class)->notify('test') — does it return true?
  2. Check storage/logs/laravel.log for the queued job's failed_jobs entry.
  3. Verify the app name is ≤ 256 chars and description ≤ 10,000 — Prowl silently truncates.
  4. Confirm the Prowl iOS app is installed and the key hasn't been regenerated on the Prowl site.
  5. If 503: you've hit the rate limit; check queue throughput.

The strategic tip: don't build a large surface area on Prowl. It's a legacy one-person service with an aging iOS client. Put your ProwlNotifier interface in place from day one, use it as a swappable backend behind a config flag, and treat migration to ntfy, Pushover, or APNs-direct as a scheduled initiative — not something you'll "get to." The interface you write in an afternoon is the only thing that survives the vendor.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
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