Akka out of the box provides At-Most-Once delivery, as you've discovered. At-Least-Once is available in some libraries such as Akka Persistence, and you can create it yourself fairly easily by creating an ACK-RETRY protocol in your actors. The Sender keeps periodically sending the message until the receiver acknowledges receipt of it.
Put simply, for At-Least-Once the responsibility is with the Sender. E.g in Scala:
class Sender(receiver: ActorRef) extends Actor {
var acknowledged = false
override def preStart() {
receiver ! "Do Work"
system.scheduler.scheduleOnce(50 milliseconds, self, "Retry")
}
def receive = {
case "Retry" =>
if(!acknowledged) {
receiver ! "Do Work"
system.scheduler.scheduleOnce(50 milliseconds, self, "Retry")
}
case "Ack" => acknowledged = true
}
}
class Receiver extends Actor {
def receive = {
case "Do Work" =>
doWork()
sender ! "Ack"
}
def doWork() = {...}
}
But with At-Most-Once processing, the receiver has to ensure that repeated instances of the same message only result in work being done once. This could be achieved through making the work done by the receiver idempotent so it can be repeatedly applied, or by having the receiver keep a record of what it has processed already. For At-Most-Once the responsibility is with the receiver:
class AtMostOnceReceiver extends Actor {
var workDone = false
def receive = {
case "Do Work" =>
if(!workDone) {
doWork()
workDone = true
}
sender ! Ack
}
}