In a previous article, we introduced the Messenger outbox, which saves the messages you send in the same database transaction as your changes. Symfony 8.2 adds the other half of the pattern: an inbox to keep track of messages your application has already processed.
Skipping Redelivered Messages
Brokers deliver messages at least once. If a worker commits the changes of its handlers but crashes (or loses the connection) before acknowledging the message, the broker delivers it again and the handlers run twice. That's how a customer ends up charged twice or receives the same email twice. Until now, you had to make each handler idempotent yourself.
The new inbox transport option lets Messenger recognize these redeliveries and
skip handlers that have already completed successfully. Inside the transaction that
runs your handlers, Symfony stores the transport name and the message id in a
messenger_inbox database table. When a redelivered message is already in that
table, its handlers are skipped and the message is acknowledged:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
# config/packages/messenger.yaml
framework:
messenger:
# adds the message id that the inbox needs to every message
identity_stamps: true
transports:
orders:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
inbox: true
# or: inbox: { connection: billing }
buses:
messenger.bus.default:
# the inbox needs your handlers to run in a database transaction
middleware: [doctrine_transaction]
If a handler fails, the transaction rolls back both its changes and the inbox entry, so the message can be processed again on the next retry. If two workers receive the same message, the second waits for the first worker's transaction to finish.
Run messenger:setup-transports to create the inbox table. The table is also
included in your Doctrine schema, so you can create it through a migration.
Things to keep in mind about the inbox:
- it requires the Doctrine Messenger bridge (
symfony/doctrine-messenger) and must use the same database connection as your handlers; retention(7 days by default) must be longer than the time your transport can take to redeliver a message;- messages retried from a failure transport are recorded under the transport where they originally failed;
- messages handled by batch handlers are not recorded in the inbox.
Messages dispatched with DispatchAfterCurrentBusStamp must go to a transport
that uses an outbox. The inbox entry is committed before these messages are sent,
so a failed send could otherwise lose them. Messenger refuses to send them
without an outbox.