The thing is, "exactly once" isn't really possible, with a generic message queuing system. Let me explain briefly one way you need to think about distributed systems:
The network could cut at any moment during a transaction.
If message consumers don't send acknowledgements, and the queue considers the message sent as soon as it has been successfully written to the network connection, then it might never really be delivered and processed. You can't be sure if it really got to the destination before the network cut out, or something crashed. This is "at most once" - it may have been processed once, or not at all.
If message consumers acknowledge messages after they've finished processing them, and the queue waits for that, then the acknowledgement might be the thing that is lost. The queue sends the message to a different consumer if it doesn't get an acknowledgement before a timeout, but the message might have been processed already, and the consumer crashed or the network partitioned just before the acknowledgement. This is "at least once".
You could consider a final scheme - consumers acknowledge receiving the message, before they process it. The process could crash just after the acknowledgement, or the ack could be lost without it knowing. So this isn't "at least once" or "at most once", it's "no idea".
Or maybe they even wait for an ack that the server got the ack, before processing. The process could still crash after sending its ack or after receiving the other ack, this is still just "at most once".
"At most once" is usually not useful. Usually you want "at least once", and then logic specific to your data to resolve "more than once" in an acceptable way.