I have never tried Erlang, but I have read that it doesn't have static type checking. How does it guarantee that different services are following protocols?
Erlang is dynamically typed in part, I believe, to allow hot-swapping code in a running system. It relies on pattern matching to ensure code contracts / ie your types are more like guarantees that a pattern holds true, even if some of the specifics of that protocol have changed. Thus, with zero downtime, you can make updates to the system, while still knowing that your assertions about received data matching your protocol remains true. Any adapters can act like both type update and schema migration simultaneously, as in the case where you wish to support multiple versions of an API simultaneously.
The runtime system has built-in support for concurrency, distribution and fault tolerance. Because of the design goals for Erlang and it's runtime, you get services that can all run on one system or be distributed across a network, but the code that you actually write is relatively simple; the entire distributed system acts as a fault-tolerant VM that has functional guarantees.
If your startup node fails, then other nodes are elected. If a node crashes while in the middle of a method, another node will execute the method instead.
The runtime itself has some analogies to functional coding styles. It runs on a register machine rather than a stack machine. The call and return sequence is replaced by direct jumps to the implementation of the next instruction.