symfony/amazon-sqs-messenger
Amazon SQS transport for Symfony Messenger, with FIFO and fair-queue support, X-Ray trace stamps, receipt-handle handling, and middleware for automatic FIFO group and deduplication IDs. Includes AmazonSqsFifoStamp, AmazonSqsFairQueueStamp, AmazonSqsXrayTraceHeaderStamp, and AmazonSqsReceivedStamp.
symfony/amazon-sqs-messenger is the SQS transport for Symfony Messenger. In a Laravel app you normally pull it in alongside the Messenger component (it brings async-aws/sqs + async-aws/core with it):
composer require symfony/messenger symfony/amazon-sqs-messenger
Register the transport factory once in a service provider, then build a transport from a DSN:
use Symfony\Component\Messenger\Bridge\AmazonSqs\Transport\AmazonSqsTransportFactory;
use Symfony\Component\Messenger\Transport\TransportFactory;
$this->app->singleton(AmazonSqsTransportFactory::class);
$this->app->singleton(TransportFactory::class, fn ($app) => new TransportFactory([
$app->make(AmazonSqsTransportFactory::class),
]));
// config/messenger.php or a dedicated config file
'orders' => [
'dsn' => env('SQS_DSN', 'sqs://default?region=eu-west-1&queue_name=orders'),
],
'orders_fifo' => [
'dsn' => 'sqs-fifo://default?region=eu-west-1&queue_name=orders.fifo',
],
Where to look first, in this order:
AmazonSqsTransportFactory — the DSN parser; every query option you can set lives here.AmazonSqsTransport — wires sender/receiver, implements SetupableTransportInterface (this is what messenger:setup-transports calls to create the queue).SqsSender / SqsReceiver / SqsConnection — the actual SendMessage/ReceiveMessage/DeleteMessage calls.AmazonSqsException — has isMissingQueue(), isInvalidParameter(); catch these to distinguish "queue not provisioned" from "bad payload".First use case — one queue, one worker. Create the queue, dispatch, consume:
php artisan messenger:setup-transports # or create the queue in Terraform/CDK
php artisan messenger:consume orders -vv
Keep messenger:consume under Supervisor/systemd as a long-running daemon — it is not a queue:work equivalent and does not use Laravel's queue tables, events, or failed_jobs.
Pick this transport over Laravel's native sqs queue driver when you need FIFO semantics, per-message groups, or explicit deduplication. The native driver treats SQS as a simple job feed; this package exposes the SQS concepts directly.
Dispatching with stamps. Behaviour is driven by stamps on the envelope, not by config:
use Symfony\Component\Messenger\Bridge\AmazonSqs\Transport\AmazonSqsFifoStamp;
use Symfony\Component\Messenger\Stamp\DelayStamp;
$bus->dispatch(new SyncOrder($id), [
new AmazonSqsFifoStamp(messageGroupId: 'customer-'.$customerId),
new DelayStamp(30_000), // ms, SQS caps DelaySeconds at 900s
]);
DelayStamp above 900 000 ms silently degrades — for anything longer, schedule it (scheduler, Step Functions, or a delayed re-dispatch) rather than hoping SQS accepts it.
Wrap the Messenger worker in an Artisan command. Laravel's queue:work knows nothing about Messenger, so you own the loop:
$this->app->make(ConsumeMessagesCommand::class);
A thin command that resolves the transport by name and delegates to Symfony\Component\Messenger\Worker gives you --time-limit, --memory-limit, and StopWorkerOnSignalsListener for free — which is what you want for graceful container shutdowns.
Serializer choice is an integration decision, not a detail. The default PhpSerializer is opaque to other consumers and unsafe across trust boundaries; use Symfony\Component\Messenger\Transport\Serialization\Serializer with Symfony's JSON serializer (or your own Laravel-aware encoder) when non-PHP services (Node, Go, Lambda) read the same queue. Headers are shipped in the x-symfony-messenger message attribute, so non-Messenger consumers must ignore it and read only the body.
Retries and failures are an AWS concern. This transport does not maintain a failed-message table — a nacked message returns to the queue after the visibility timeout. Configure a redrive policy with maxReceiveCount plus a DLQ on the queue itself, and inspect/redrive from there. Optionally catch RejectMessageExceptionInterface in a middleware to delete poison messages rather than letting them cycle.
Local development: point the DSN at LocalStack or ElasticMQ instead of mocking the SDK. It exercises the real code path including attribute serialisation:
sqs://test:test@default/orders?region=us-east-1&endpoint=http://localhost:4566
Keep that DSN in .env.local only — the endpoint option bypasses normal AWS endpoint resolution.
Consumer sizing. Run several messenger:consume processes for throughput; for FIFO queues, parallelism only helps across distinct MessageGroupIds, so shard your group IDs deliberately.
SqsSender throws when the encoded body plus attributes exceeds it. If payloads can grow, use a claim-check pattern (store the blob in S3, send the key) instead of chasing compression.visibility_timeout (default 30s) is shorter than your handler, SQS redelivers mid-processing and you get duplicates and double side effects. Set it to your p99 handler duration, or extend it from inside long handlers. Make handlers idempotent regardless.stopwaitsecs in Supervisor must exceed the longest in-flight request, otherwise SIGKILL lands mid-ack and the message is redelivered. Since v8.1.8, Symfony holds pending signals until the in-flight SQS request (send/delete) finishes, so a SIGTERM arriving during an acknowledgement no longer aborts the HTTP call — but that protection only covers the request itself, not your handler.maxReceiveCount is hit. Always have a DLQ before deploying a serializer change, and ship the change in both directions before rolling consumers.sqs-fifo:// scheme. Using sqs:// against a .fifo queue fails at send time; the scheme is what makes the transport attach MessageGroupId/MessageDeduplicationId. Deduplication only covers a 5-minute window — don't treat it as exactly-once.wait_time defaults to 20s, which is correct for cost; a tiny poll_timeout on an empty queue spins CPU. Prefer fewer, longer-polling workers over many short-polling ones. buffer_size controls how many messages a single receive batches — raise it for high-throughput, low-latency-tolerant workloads.messenger:setup-transports in deploy or (better) manage queues via IaC, and let the app fail fast with AmazonSqsException::isMissingQueue().access_key/secret_key entirely and let the SDK chain resolve them. When you must use temporary credentials, pass session_token, and rotate via env not config cache.debug=1 to the DSN to get HTTP-level logging from async-aws, and route the Symfony\Component\Messenger namespace to a dedicated log channel. For "message never arrives", check the SQS console's NumberOfMessagesSent before blaming the transport.SqsSender/SqsReceiver, add middleware for correlation-ID attributes, or swap AmazonSqsTransportFactory for a subclass if you need DSN options the package doesn't expose (custom endpoints, per-queue tags). Everything is constructor-injected, so container bindings are sufficient — no forking required.How can I help you explore Laravel packages today?