mcfedr/sqs-queue-driver-bundle
Symfony bundle that lets you use Amazon SQS as your queue driver, so you can offload background jobs to AWS without rewriting your existing messaging code.
This is a Symfony bundle, not a Laravel package. A Laravel developer's first job is to decide whether it fits at all: it plugs into symfony/messenger and the Symfony DI container. If you are in a Laravel-only codebase, stop here and use Laravel's built-in SQS queue driver (config/queue.php, php artisan queue:work sqs) — this bundle gives you nothing Laravel hasn't already shipped. It becomes interesting only when you are working in a Symfony app (or a hybrid app that already boots a Symfony kernel alongside Laravel, e.g. shared workers/legacy components).
Mental model translation for a Laravel dev:
| Laravel | Symfony Messenger (this bundle) |
|---|---|
Queue connection (sqs) |
Transport (sqs://... DSN) |
| Job class | Message class (plain DTO) |
dispatch(new Job) |
$bus->dispatch(new Message) |
php artisan queue:work |
bin/console messenger:consume <transport> |
failed_jobs table |
Failure transport / DLQ |
queue:retry |
messenger:failed:retry |
Minimal steps:
composer require mcfedr/sqs-queue-driver-bundle. Symfony Flex should register it in config/bundles.php; if not, add Mcfedr\SqsQueueDriverBundle\...Bundle::class => ['all' => true].symfony/messenger is installed and you have AWS credentials in the standard chain (env vars, ~/.aws, or an instance/task IAM role).config/packages/messenger.yaml using the DSN the bundle registers (typically sqs://... with region, queue_name, and credentials resolved via the AWS SDK/async-aws credential chain).# config/packages/messenger.yaml
framework:
messenger:
transports:
async_sqs:
dsn: '%env(SQS_DSN)%'
options:
queue_name: 'app-tasks'
region: 'eu-west-1'
routing:
'App\Message\SendInvoice': async_sqs
First use case: a single fire-and-forget job (e.g. "send invoice email") dispatched from a controller/handler and consumed by a long-running messenger:consume async_sqs worker under Supervisor/systemd.
Where to look first: the bundle's DependencyInjection/*Extension.php to see exactly which transport factory/service it registers and which options it accepts, then the README of the underlying mcfedr/sqs-queue-driver library. Do not trust the bundle README alone — with 4 stars and 0 dependents, verify option names against the actual code.
Message as a thin DTO. Keep messages small and serializable (scalars/arrays, not Eloquent/Doctrine entities). Symfony Messenger serializes to JSON by default; entities become stale IDs by the time a worker runs.
final class SendInvoice
{
public function __construct(public string $invoiceId) {}
}
Dispatch from anywhere via the bus, not directly through the transport:
public function __construct(private MessageBusInterface $bus) {}
$this->bus->dispatch(new SendInvoice($id));
Worker lifecycle. Run messenger:consume as a managed daemon. In Laravel terms this is queue:work --daemon; Symfony already loops, so you add guardrails:
bin/console messenger:consume async_sqs --time-limit=3600 --memory-limit=256M -vv
Wrap with Supervisor (or systemd) with autorestart, and send SIGTERM for graceful shutdown — Symfony's worker finishes the in-flight message before exiting.
Environment-specific transport switching. Mirror Laravel's QUEUE_CONNECTION=sync trick: route to sync:// in dev/test and to sqs:// in prod. This keeps local development fast and avoids burning AWS calls in tests.
# config/packages/test/messenger.yaml
framework:
messenger:
transports:
async_sqs: 'in-memory://'
Multiple queues for priority/isolation. Register several transports (async_sqs_high, async_sqs_low) pointing at different SQS queues, route expensive jobs to a dedicated queue, and run separate worker pools. This is how you avoid one slow job class starving everything else — the same reason you'd split Laravel queues.
Retries and failure handling. Symfony Messenger has its own retry strategy on the transport, and SQS has its own redrive policy/DLQ. Use one, not both, or you get confusing double-retry behaviour. Either let Messenger retry in-process (retry_strategy on the transport) or let SQS move messages to a DLQ after maxReceiveCount and have a separate consumer for it. Pick a single owner.
Idempotency. SQS is at-least-once. Any handler that charges a card, sends an email, or writes to an external system must be idempotent (dedup key in Redis/DB, or an upsert). This is not optional in production.
Bridging into a Laravel app. Don't try to install this bundle inside Laravel's container. If you genuinely need Symfony's worker behaviour alongside Laravel, run the Symfony console process as a separate service writing to/reading from the same SQS queue, and agree on a message envelope (JSON schema, type field, serializer compatibility) with your Laravel queue:work sqs consumers. Mixing Symfony's default serializer with Laravel's SerializesModels payload format will not work — standardise explicitly.
Observability. Hook WorkerMessageFailedEvent, WorkerMessageHandledEvent, and WorkerRunningEvent to emit metrics/logs. This is the equivalent of Laravel's queue events; it's where you add timing and alerting.
ChangeMessageVisibility) for long jobs.sqs:ReceiveMessage, sqs:DeleteMessage, sqs:ChangeMessageVisibility, sqs:GetQueueUrl, and the producer needs sqs:SendMessage (+ sqs:GetQueueAttributes if you use queue setup). Missing ChangeMessageVisibility is a common silent failure.AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY or a shared profile. A mismatch produces confusing "queue not found" errors rather than auth errors.MessageGroupId (and dedup IDs). Keep payloads as references (IDs/paths) rather than blobs.auto_setup style option), leave it off in production. Queue creation in a worker startup path is a permission and race-condition hazard.-vv on the consumer to see dispatch/ack logs, inspect the failure transport (messenger:failed:show), and check CloudWatch NumberOfMessagesReceived / ApproximateAgeOfOldestMessage to distinguish "worker down" from "worker slow". ApproximateAgeOfOldestMessage climbing is your queue-backlog alarm.SIGTERM (not SIGKILL) so in-flight messages finish. --time-limit lets a worker exit cleanly and be restarted, which is the safest way to avoid memory leaks in long-running PHP.How can I help you explore Laravel packages today?