bref/symfony-messenger
Bridge for running Symfony Messenger on AWS Lambda with Bref. Dispatch messages to SQS, SNS, or EventBridge and let Lambda workers consume them, with setup documented on the Bref website.
What it actually is: a Symfony Messenger transport/consumer bridge for Bref. It gives you three TransportInterface implementations (sqs://, sns://, eventbridge://) for sending, and three Lambda handlers (SqsConsumer, SnsConsumer, EventBridgeConsumer) that turn a Lambda event batch into $bus->dispatch() calls. It is Symfony-only — a Laravel app cannot use it with Laravel's queue system (see Gotchas).
Minimal path:
composer require bref/bref bref/symfony-messenger symfony/messenger (Symfony 5.4+/6/7). Flex/bundle registration wires the transport factories and consumer services; if you're on a non-Flex app, register the bundle manually.config/packages/messenger.yaml:
framework:
messenger:
transports:
async:
dsn: 'sqs://'
options:
queue_name: 'orders'
routing:
'App\Message\SendInvoice': async
(SNS/EventBridge DSNs take topic ARN / event bus name instead — the only real documentation is at bref.sh/docs/symfony/messenger, not in the repo README. Check option names there before guessing.)# serverless.yml
functions:
web:
handler: public/index.php
runtime: php-83-fpm
events: [{ httpApi: '*' }]
worker:
handler: bin/worker.php
runtime: php-83
timeout: 30
events:
- sqs:
arn: !GetAtt OrdersQueue.Arn
batchSize: 10
<?php // bin/worker.php
use Bref\Symfony\Messenger\Service\Sqs\SqsConsumer;
require __DIR__.'/../vendor/autoload.php';
(new Symfony\Component\Dotenv\Dotenv())->bootEnv(__DIR__.'/../.env');
$kernel = new App\Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$kernel->boot();
return $kernel->getContainer()->get(SqsConsumer::class);
SendInvoice → SQS → worker Lambda runs #[AsMessageHandler] SendInvoiceHandler. That's the whole loop; everything else is configuration.Dispatch vs. consume are separate functions. Treat the HTTP function and the worker function as two deployables. The web function only ever send()s; the worker function only ever handles records. Don't try to do both in one Lambda.
Pick the transport by fan-out semantics.
sqs:// — single work queue, one consumer, redrive policy for retries. This is your Laravel queue:work equivalent.sns:// — pub/sub fan-out to multiple SQS queues or Lambdas; use when several services care about one event.eventbridge:// — event bus with content-based routing rules; use for domain events crossing service boundaries.Handlers are ordinary Messenger handlers, so keep the business logic out of the Lambda layer:
#[AsMessageHandler]
final class SendInvoiceHandler
{
public function __construct(private InvoiceMailer $mailer) {}
public function __invoke(SendInvoice $message): void { $this->mailer->send($message->id); }
}
One consumer class per Lambda. SqsConsumer, SnsConsumer, EventBridgeConsumer each expect a specific record shape; create three functions (worker-sqs, worker-sns, worker-events) rather than branching on payload.
Local development. The Bref transports are effectively send-only — consumption happens through the Lambda consumer, so bin/console messenger:consume async against sqs:// is not your dev loop. Instead:
in-memory:// or doctrine:// in config/packages/dev/messenger.yaml and consume locally as normal;Testing.
// config/packages/test/messenger.yaml → dsn: 'in-memory://'
$transport = $container->get('messenger.transport.async');
$bus->dispatch(new SendInvoice(42));
self::assertCount(1, $transport->getSent());
For the consumer, hydrate a Bref event from a fixture and invoke it directly — no AWS needed:
$consumer->handle(new SqsEvent(['Records' => [$record]]), new Context());
Infrastructure stays yours. The package does not create queues, topics, subscriptions, or DLQs. Declare them in serverless.yml resources / Terraform / CDK alongside the function, and reference ARNs via env vars.
Extension point: implement TransportInterface + a Messenger transport factory for anything else in AWS (Kinesis, MSK), or decorate a consumer to add logging/tracing around the dispatch loop.
MessageBusInterface ≠ Bus::dispatch, handlers ≠ Jobs, there is no queue:work, no failed_jobs table, no Horizon. A Laravel shop should use bref/laravel-bridge with Laravel's native SQS driver; only reach for this package in a Symfony codebase (or a polyglot monorepo where the worker is Symfony).retry_strategy. Messenger's retry_strategy and failure_transport are dead configuration here — the consumer throws, Lambda marks the batch failed, SQS re-delivers after the visibility timeout until maxReceiveCount, then the redrive policy sends it to the DLQ. Set the redrive policy explicitly or you get an infinite retry loop on poison messages.ReportBatchItemFailures, enable it and return the failed item identifiers; otherwise make every handler idempotent (dedupe on a business key or the Messenger envelope's message id, not the SQS MessageId alone).bin/worker.php boots the full kernel on every cold start. Run composer dump-env prod and composer install --classmap-authoritative --no-dev in the build to shave hundreds of ms.sqs:SendMessage/ReceiveMessage/DeleteMessage (or sns:Publish, events:PutEvents) on the exact ARNs.Records[].Sns), the EventBridge consumer sees detail. A rule with a too-narrow event pattern or an SNS subscription filter will silently drop messages you thought you were sending — verify end-to-end with a real deploy before trusting it.serverless invoke local (or bref local) for the consumer, CloudWatch Logs for the real thing (the Context object gives you the request id to correlate). A message that "disappeared" is almost always a routing rule, a filter policy, or a DLQ — check the queue's metrics before the code.composer.json, and read the CHANGELOG on upgrade — there is not much community content to fall back on.How can I help you explore Laravel packages today?