our-edu/laravel-sqs-messaging
Laravel SQS Messaging Package
A comprehensive Laravel package for AWS SQS messaging with RabbitMQ rollback support, idempotency, error handling, and CloudWatch metrics integration.
Features
- ✅ AWS SQS integration with automatic queue/DLQ creation
- ✅ Message envelope with idempotency keys
- ✅ Redis + Database idempotency checking
- ✅ Error classification (validation, transient, permanent)
- ✅ CloudWatch metrics integration
- ✅ Dead Letter Queue (DLQ) management
- ✅ Long polling support
- ✅ RabbitMQ adapter for easy migration
- ✅ Environment-aware queue naming
Installation
Via Composer
composer require our-edu/laravel-sqs-messaging:3.*
Configuration
1. Publish Configuration
php artisan vendor:publish --provider="OurEdu\SqsMessaging\SqsMessagingServiceProvider"
2. Environment Variables
Add to your .env:
# AWS Credentials (required)
AWS_SQS_DEFAULT_REGION=us-east-2
AWS_SQS_ACCESS_KEY_ID=your-access-key
AWS_SQS_SECRET_ACCESS_KEY=your-secret-key
AWS_DEFAULT_REGION=me-central-1
SQS_ALLOW_TIMESTAMP_ATTRIBUTE=false # Set to true if you want to use the timestamp attribute for idempotency
# SQS Configuration
SQS_QUEUE_PREFIX=staging # or production, dev, etc.
# Messaging Driver
MESSAGING_DRIVER=sqs # Use SQS (default) or rabbitmq for rollback
MESSAGING_FALLBACK_TO_RABBITMQ=true # Enable fallback to RabbitMQ when SQS queue doesn't exist
# CloudWatch Metrics (optional)
SQS_CLOUDWATCH_ENABLED=true
SQS_CLOUDWATCH_NAMESPACE=SQS/PaymentService
Notes:
1- Set MESSAGING_FALLBACK_TO_RABBITMQ=true if you have other projects still using RabbitMQ. This ensures messages go to RabbitMQ when SQS queue doesn't exist.
2-Set SQS_ALLOW_TIMESTAMP_ATTRIBUTE=true and append timestamp attribute to the payload of the message if you want to generate the IdempotencyKey based on the timestamp.
3. Configure Queues
Edit config/sqs_queues.php:
HINT: This File Defines The SQS Queues Used By Your Application
return [
'payment' => [
'default' => 'payment-service-queue',
'specific' => [
'refunds' => 'payment-service-refunds-queue',
],
],
];
4. Configure Event Mappings
Edit config/sqs_events.php:
HINT: This File Maps Events To Their Respective Listeners ( used in receiving messages )
return [
'admission:admission:assign.subscription.services' => CreateStudentSchoolSubscriptionListener::class,
'admission:admission:cancel.application' => CancelStudentSubscriptionServicesListener::class,
];
5. Configure Target Queues
Edit config/sqs_target_queues.php:
HINT: This File Maps Events To Their Target Queues ( used in publishing messages )
return [
'payment:payment:student.subscribe.and.pay' => 'admission-service-queue',
'payment:payment:cancel.students' => 'admission-service-queue',
];
6. Run Migration
php artisan migrate
This creates the processed_events table for idempotency.
7. Ensure Queues Exist
php artisan sqs:ensure
For Production: Add this to your entrypoint.sh or container startup script to ensure queues exist before starting consumers:
php artisan sqs:ensure || echo "Warning: SQS queue ensure failed, but continuing startup..."
Usage
Publishing Messages
Option 1: Use Unified MessagingService (Recommended)
The package includes a MessagingService that can switch between available drivers (SQS and RabbitMQ).
Step 1: Update your Support\RabbitMQ\Publishable trait and add the following method:
public static function publishFromInstance(object $event): void
{
Container::getInstance()
->make(Publisher::class)
->publish($event);
}
Complete updated trait:
<?php
declare(strict_types=1);
namespace Support\RabbitMQ;
use Illuminate\Container\Container;
use Illuminate\Contracts\Container\BindingResolutionException;
trait Publishable
{
/**
* @return void
* @throws BindingResolutionException
*/
public static function publish(): void
{
Container::getInstance()
->make(Publisher::class)
->publish(new static(...func_get_args()));
}
public static function publishFromInstance(object $event): void
{
Container::getInstance()
->make(Publisher::class)
->publish($event);
}
}
Step 2: In your listeners where you publish a message, replace RabbitMQ publishing code: HINT: Use The Same Payload Constructor Parameters Old RabbitMQ Publisher:
Notification::publish($queueName, $payload)
New MessagingService Publisher:
app(MessagingService::class)->publish(
event: new Notification($queueName, $payload),
eventClassReference: Notification::class
);
Example Migration: HINT: Use The Same Payload Constructor Parameters ( $event->reversePaymentRequest, $usersUuids ) Old RabbitMQ Listener:
namespace Domain\Payments\Listeners\ReversePaymentRequest;
use App\BaseApp\Enums\RoleEnum;
use Domain\Models\User;
use Domain\Payments\Events\ReversePaymentRequest\ReversePaymentRequestCreatedEvent;
use Domain\Payments\Notifications\ReversePaymentRequestNotification;
use Illuminate\Contracts\Container\BindingResolutionException;
class ReversePaymentRequestCreatedListener
{
/**
* @throws BindingResolutionException
*/
public function handle(ReversePaymentRequestCreatedEvent $event): void
{
$users = User::query()->whereHas('roles', function ($query) {
$query->whereIn('name', [RoleEnum::ACCOUNTANT_MANAGER, RoleEnum::ACCOUNTANT_MANAGER]);
})->get();
$usersUuids = $users->pluck('uuid')->toArray();
ReversePaymentRequestNotification::publish($event->reversePaymentRequest, $usersUuids);
}
}
New Listener using MessagingService:
namespace Domain\Payments\Listeners\ReversePaymentRequest;
use App\BaseApp\Enums\RoleEnum;
use Domain\Models\User;
use Domain\Payments\Events\ReversePaymentRequest\ReversePaymentRequestCreatedEvent;
use Domain\Payments\Notifications\ReversePaymentRequestNotification;
use Illuminate\Contracts\Container\BindingResolutionException;
use OurEdu\SqsMessaging\MessagingService;
class ReversePaymentRequestCreatedListener
{
/**
* @throws BindingResolutionException
*/
public function handle(ReversePaymentRequestCreatedEvent $event): void
{
$users = User::query()->whereHas('roles', function ($query) {
$query->whereIn('name', [RoleEnum::ACCOUNTANT_MANAGER, RoleEnum::ACCOUNTANT_MANAGER]);
})->get();
$usersUuids = $users->pluck('uuid')->toArray();
// New messaging service publish
app(MessagingService::class)->publish(
event: new ReversePaymentRequestNotification($event->reversePaymentRequest, $usersUuids), // HINT: Use The Same Payload Constructor Parameters
eventClassReference: ReversePaymentRequestNotification::class
);
}
}
Switch drivers via environment variable:
MESSAGING_DRIVER=sqs # Use SQS (default)
MESSAGING_DRIVER=rabbitmq # Rollback to RabbitMQ
Option 2: Direct SQS Adapter
HINT: This approach will publish directly to SQS (bypasses driver switching):
use App\Events\StudentEnrolled;
use OurEdu\SqsMessaging\Drivers\Sqs\SQSPublisherAdapter;
$adapter = app(SQSPublisherAdapter::class);
$adapter->publish(new StudentEnrolled($student), 'payment-service-queue');
Consuming Messages
Add to Supervisor configuration:
[program:sqs-payment-consumer]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/artisan sqs:consume payment-service-queue
autostart=true
autorestart=true
user=root
numprocs=1
stdout_logfile=/var/www/storage/logs/payment-consumer.log
Available Commands
# Ensure all queues exist
php artisan sqs:ensure
# Consume messages from a queue
php artisan sqs:consume {queue}
# Test AWS connection
php artisan sqs:test:connection
# Inspect DLQ messages
php artisan sqs:inspect-dlq {queue}
# Monitor DLQ depth
php artisan sqs:monitor-dlq
# Replay DLQ messages
php artisan sqs:replay-dlq {queue} --limit=10
# Cleanup old processed events
php artisan sqs:cleanup-processed-events --days=7
Rollback & Cleanup
See ROLLBACK_GUIDE.md for detailed instructions on:
- Rolling back from SQS to RabbitMQ
- Cleaning up RabbitMQ code once SQS is stable
- Dual write mode for gradual migration
Production Deployment
Ensure Queues Before Startup
Add to your production entrypoint.sh or container startup script:
php artisan sqs:ensure || echo "Warning: SQS queue ensure failed, but continuing startup..."
This ensures all required SQS queues and DLQs exist before starting consumers or the application.
Production Environment Variables
# AWS Authentication (REQUIRED)
AWS_SQS_ACCESS_KEY_ID=your-production-access-key
AWS_SQS_SECRET_ACCESS_KEY=your-production-secret-key
AWS_DEFAULT_REGION=us-east-2
# Production Settings
SQS_QUEUE_PREFIX=production
SQS_AUTO_ENSURE=false # Disable auto-creation in production
SQS_CLOUDWATCH_ENABLED=true # Enable monitoring
MESSAGING_DRIVER=sqs # Use SQS
MESSAGING_DUAL_WRITE=false # Disable dual write
MESSAGING_FALLBACK_TO_RABBITMQ=true # Enable fallback to resolve other projects that work with rabbitmq
For detailed production deployment guide, see DEPLOYMENT.md.
Requirements
- PHP ^8.1
- Laravel ^9.0|^10.0
- AWS SDK PHP ^3.0
- Redis (for idempotency)
- AWS SQS access
License
MIT
Support
For issues and questions, please contact the OurEdu development team.
Related Packages
Powerful PHP database abstraction layer (DBAL) with many features for database s...
Laravel Serializable Closure provides an easy and secure way to serialize closur...
Cli error handling for console/command-line PHP applications.
Version History
| Version | Released | PHP | Laravel | License |
|---|---|---|---|---|
| 3.0.1 | ^8.1 | ^9.0| | MIT | |
| 3.0.0 | ^8.1 | ^9.0| | MIT | |
| 2.0.1 | ^8.1 | ^9.0| | MIT | |
| 2.0.0 | ^8.1 | ^9.0| | MIT | |
| 1.0.12 | ^8.1 | ^9.0| | MIT | |
| 1.0.11 | ^8.1 | ^9.0| | MIT | |
| 1.0.10 | ^8.1 | ^9.0| | MIT | |
| 1.0.9 | ^8.1 | ^9.0| | MIT | |
| 1.0.8 | ^8.1 | ^9.0| | MIT | |
| 1.0.7 | ^8.1 | ^9.0| | MIT | |
| 1.0.1 | ^8.1 | ^9.0| | MIT | |
| 1.0.0 | ^8.1 | ^9.0| | MIT |