The reasons against transactions are covered in the blog post that introduced
Publisher confirms [1].
Mandatory publishing only covers routing and is orthogonal to publisher confirms.
The fundamental issue here is that you want to return a response to an HTTP client early but
a confirmation and any error handling you might want to do can happen after the request has completed.
It makes most sense to me to hand off publishing to a thread not involved in HTTP response handling
and always available. Note that if there's more than one such thread you will have concurrent publishing
which is a no-no on a shared channel and even with separate channels will affect message ordering.
The most common approach to this is putting an object to a local Queue data structure and having
a thread that continuously checks if its empty, and if not, dequeues the context object and does something,
e.g. publishes to RabbitMQ and handles errors. That can be done asynchronously (as publisher confirms inherently are)
or using Bunny::Channel#wait_for_confirms with a timeout. You can also do batching with a timeout.
Obviously error handling in such thread is very important.
More advanced techniques involve inserting a "WAL record" somewhere like a data store or local storage (if available) so
that if the request cannot complete or publishing fails or the Rails process is shut down before the message can go out,
those messages can be republished. After receiving a confirm for a message it has to be removed from "the WAL".
Actors-based solutions can be used as well but I'm not up-to-date with what's the most popular one in Ruby
these days and internally they are not significantly more sophisticated than the "a Queue and thread" example above
but might be easier to use if concurrent programming is not something you have a lot of experience with.