Exactly-once guarantees can be had the easiest with a (option 1) message queue system. This also neatly solves the problem of retries, as failed operations can be stuffed back into the queue, or a dead letter queue can be used.
You can technically run n agents on the problem (option 2), and each agent only processes users with ((int)userId) % n == agentId). Or a less naive partitioning algorithm. But you'll still have most of the issues present in a consensus algorithm.
You can use RAFT directly (option 3)
You can extend the database table with an "in processing by node id" column (option 4). The nodes can then claim rows and use the presence of their value as a precondition for further operations.
I'd probably run an upstream duplication checking algorithm so duplicates can be caught before they get sent to a customer.