basic_publish returns as soon as the message is written to the socket. The broker may not have accepted it, may not have routed it anywhere, and may have been down — none of which produces an error at the call site.
$channel->confirm_select();
$channel->set_nack_handler(function ($message) {
Log::error('broker rejected message', ['body' => $message->body]);
});
$channel->basic_publish($message, 'orders', 'order.placed');
$channel->wait_for_pending_acks(5);
Confirms are per channel and cost a round trip, which is the reason they are off by default. The other silent failure is routing: a message published to an exchange with no matching binding is discarded without complaint, and the mandatory flag with a return listener is what surfaces it. Both together are the difference between publishing and hoping.