That's more or less how erlang work: by async message-passing, and it's used to build extremely (99.9999999%) reliable softwares. A "best-effort" semantic is more than enough: when you really care if the message has been received (that is to say most of the time), you make a "synchronous" call, which is really just two async calls (one in each direction), with a timeout. Even if the message back is just an acknowledgement that the message was received.
In Erlang/OTP it's wrapped in an actor behaviour called "gen_server", and the default timeout is 5000 milliseconds. If it timeouts an exception is raised, and a common strategy is to simply let the process crash, and restart to try again [0].
On the other hand it's not async call all the way down: it's only the calls between actors that are asynchronous: if you write a loop [1] within your actor then it's synchronous. To write asynchronous code you need to spawn new actors (which is trivial, and will always by default result in an exception being raised if something get lost or timeout after 5 seconds).
Note that when you write Erlang code the granularity of an actor is absolutely not the same as when you write OOP code: you don't wrap every data structure in a process, rather processes tend to be about processing data, or abstracting an IO point (a file, a socket, etc.) since the semantic is perfect.
[0] The implementation of all of this takes more or less 1 line of code, this is really the defaults.
[1] you need to write it recursively in Erlang.