Laravel backend package for sending push notifications through one selected provider at a time.
Supported providers:
| Provider | Scheduling |
|---|---|
| π₯ Firebase Cloud Messaging (FCM) | Via scheduled command |
| π£ OneSignal | Native send_after |
pushify/
βββ config/
β βββ pushify.php
βββ database/
β βββ migrations/
β βββ 2026_01_01_000000_create_pushify_notifications_table.php
βββ routes/
β βββ pushify.php
βββ src/
β βββ Commands/
β β βββ SendScheduledPushifyNotifications.php
β βββ Contracts/
β β βββ PushifyProviderInterface.php
β β βββ PushifyServiceInterface.php
β βββ Factories/
β β βββ PushifyProviderFactory.php
β βββ Http/
β β βββ Controllers/
β β β βββ PushifyController.php
β β βββ Requests/
β β β βββ StorePushifyRequest.php
β β βββ Resources/
β β βββ PushifyResource.php
β βββ Models/
β β βββ Pushify.php
β βββ Providers/
β β βββ FirebaseProvider.php
β β βββ OneSignalProvider.php
β βββ Services/
β β βββ FirebaseService.php
β β βββ OneSignalService.php
β β βββ PushifyService.php
β βββ PushifyServiceProvider.php
βββ stubs/
β βββ Http/
β β βββ Controllers/
β β β βββ PushifyController.stub
β β βββ Requests/
β β β βββ StorePushifyRequest.stub
β β βββ Resources/
β β βββ PushifyResource.stub
β βββ routes/
β βββ pushify.stub
βββ composer.json
- Requirements
- Installation
- Publish
- Migration
- Configuration
- Routes
- Usage
- Notification Statuses
- Scheduled Command
- Customizing the HTTP Layer
- Adding a Custom Provider
- Store Endpoint Payload
- Response Structure
- Authors
- License
- PHP >= 8.2
- Laravel >= 12.0
- OpenSSL PHP extension (required for Firebase JWT signing β no external Google SDK needed)
composer require badawy24/pushifyThen clear the cache:
php artisan optimize:clearphp artisan vendor:publish --tag=pushifyThis publishes only the files you are expected to edit:
config/pushify.php
routes/pushify.php
app/Http/Controllers/Pushify/PushifyController.php
app/Http/Requests/Pushify/StorePushifyRequest.php
app/Http/Resources/Pushify/PushifyResource.php
Core services, providers, factories, models, commands, and contracts stay inside the package and are never published.
# Config only
php artisan vendor:publish --tag=pushify-config
# Routes only
php artisan vendor:publish --tag=pushify-routes
# Controller, Request, Resource
php artisan vendor:publish --tag=pushify-httpphp artisan migrateCreates the pushify_notifications table:
id, title, body, image, data, scheduled_at,
status, sent_at, failed_at, error_message,
created_at, updated_at
Published config file: config/pushify.php
Only one provider is active at a time, selected from .env.
PUSHIFY_PROVIDER=firebase
FIREBASE_CREDENTIALS=storage/firebase/firebase.jsonPlace your Firebase service account JSON file at storage/firebase/firebase.json.
Firebase sends to a topic β default is all. Configure it in the published config:
'firebase' => [
'topic' => 'all',
],Your mobile app must subscribe devices to the same topic.
PUSHIFY_PROVIDER=onesignal
ONESIGNAL_APP_ID=your-app-id
ONESIGNAL_API_KEY=your-api-key
ONESIGNAL_API_URL=https://api.onesignal.com/notificationsOneSignal sends to included_segments => ['All'].
# Log full request payload β disable in production
PUSHIFY_LOG_PAYLOAD=false
# Disable package routes if you prefer to define your own
PUSHIFY_ROUTES_ENABLED=true
# Change the route prefix (default: pushify)
PUSHIFY_ROUTE_PREFIX=pushifyThe package registers these routes automatically:
GET /pushify List all notifications (paginated)
POST /pushify Create and send
GET /pushify/{pushify} Show one
POST /pushify/{pushify}/send Send an existing notification
Check registered routes:
php artisan route:list | grep pushifyAfter publishing, open routes/pushify.php and update the middleware:
Route::prefix(config('pushify.routes.prefix', 'pushify'))
->middleware(['api', 'auth:sanctum'])
->group(function () {
// routes...
});use Badawy\Pushify\Contracts\PushifyServiceInterface;The package binds this interface automatically β no manual binding required.
use Badawy\Pushify\Contracts\PushifyServiceInterface;
class OfferController extends Controller
{
public function notify(PushifyServiceInterface $push)
{
$notification = $push->sendToAll(
title: 'New offer',
body: 'Check our latest offers now',
data: [
'type' => 'offer',
'offer_id' => 15,
],
image: 'https://example.com/image.jpg',
scheduledAt: null,
);
return response()->json($notification);
}
}Both Firebase and OneSignal send immediately when scheduledAt is null.
$notification = $push->sendToAll(
title: 'Upcoming sale',
body: 'Our sale starts in one hour',
data: ['type' => 'sale'],
image: null,
scheduledAt: now()->addHour()->toDateTimeString(),
);| Provider | Behavior |
|---|---|
| π₯ Firebase | Saved as pending β sent by the command when scheduled_at <= now() |
| π£ OneSignal | Submitted immediately with native send_after β OneSignal handles the delay |
$notification = $push->create([
'title' => 'Draft notification',
'body' => 'Notification body',
'data' => ['type' => 'general'],
'image' => null,
'scheduled_at' => null,
]);Saved as pending. Nothing is sent until you manually call send() or run the command.
use Badawy\Pushify\Models\Pushify;
$notification = Pushify::findOrFail($id);
$notification = $push->send($notification);php artisan tinkerapp(\Badawy\Pushify\Contracts\PushifyServiceInterface::class)
->sendToAll(
title: 'Hello',
body: 'Test from Tinker',
data: ['type' => 'test'],
image: null,
scheduledAt: null,
);// Store a notification record without sending
public function create(array $payload): Pushify;
// Create and send (or schedule) in one call
public function sendToAll(
string $title,
string $body,
array $data = [],
?string $image = null,
?string $scheduledAt = null
): Pushify;
// Send an existing stored notification
public function send(Pushify $notification): Pushify;
// Mark due OneSignal scheduled notifications as sent locally
public function markScheduledAsSent(): int;| Status | Meaning |
|---|---|
pending |
Stored, not sent yet |
processing |
Currently being dispatched to the provider |
scheduled |
Submitted to OneSignal with a future send_after |
sent |
Successfully delivered to the provider |
failed |
Failed β see error_message column in the database |
php artisan pushify:send-scheduled| Provider | What it does |
|---|---|
| π₯ Firebase | Sends all pending notifications where scheduled_at <= now() |
| π£ OneSignal | Marks all scheduled notifications where scheduled_at <= now() as sent locally β OneSignal already delivered them |
In routes/console.php:
Schedule::command('pushify:send-scheduled')->everyMinute();Or via cron:
* * * * * php /path/to/project/artisan pushify:send-scheduled >> /dev/null 2>&1After publishing with --tag=pushify-http, you can freely edit:
| File | Purpose |
|---|---|
app/Http/Controllers/Pushify/PushifyController.php |
Request handling & response |
app/Http/Requests/Pushify/StorePushifyRequest.php |
Validation rules & authorization |
app/Http/Resources/Pushify/PushifyResource.php |
JSON output shape |
The published controller injects PushifyServiceInterface β extend or replace any logic without touching the package internals.
Step 1 β Create your provider class:
namespace App\Pushify\Providers;
use Badawy\Pushify\Contracts\PushifyProviderInterface;
class CustomProvider implements PushifyProviderInterface
{
public function sendToAll(
string $title,
string $body,
array $data = [],
?string $image = null,
?string $scheduledAt = null
): array {
// Your HTTP call or SDK integration here
return ['success' => true];
}
}Step 2 β Register it in config/pushify.php:
'providers' => [
'firebase' => \Badawy\Pushify\Providers\FirebaseProvider::class,
'onesignal' => \Badawy\Pushify\Providers\OneSignalProvider::class,
'custom' => \App\Pushify\Providers\CustomProvider::class,
],Step 3 β Activate it in .env:
PUSHIFY_PROVIDER=customPOST /pushify
{
"title": "New offer",
"body": "Check our latest offers",
"image": "https://example.com/image.jpg",
"data": {
"type": "offer",
"offer_id": 15
},
"scheduled_at": null
}Scheduled example:
{
"title": "Scheduled offer",
"body": "This will be sent later",
"image": null,
"data": { "type": "offer" },
"scheduled_at": "2026-06-01 09:00:00"
}All endpoints return a consistent JSON envelope:
{
"data": {
"id": 1,
"title": "New offer",
"body": "Check our latest offers",
"image": "https://example.com/image.jpg",
"data": { "type": "offer", "offer_id": "15" },
"scheduled_at": null,
"status": "sent",
"sent_at": "2026-01-01T12:00:00+00:00",
"failed_at": null,
"created_at": "2026-01-01T11:59:00+00:00",
"updated_at": "2026-01-01T12:00:00+00:00"
}
}MIT