There's been a lot of other BEAM articles with more details. For me, there are a few key design elements that seem atypical that weave together into a very useful environment for writing software that deals with concurrency.
Erlang's basic unit to handle concurrency is called a process which is more or less equivalent to a green thread; it has it's own execution state, heap and stack. Communication to other processes is via sending and receiving messages; there's no way to express shared memory in Erlang/Elixir (although under the hood there is some sharing at times). No shared memory makes process management simpler; if a process crashes or ends there's no dangling shared state for the environment to clean up (although, you can of course have stored references to the now dead process throughout your system). No shared memory, and immutable data structures make garbage collection fairly simple; a process can stop to GC itself, but that doesn't block any other processes. Message passing is asynchronous, which lets you chose at the sender if you want to send a message and wait for the response, or send it and check for the response later; processes are not able to be interrupted, except by death, so the control flow is always clear, you can check for messages wherever you want, but you do have to check. I really like the built in support for hot loading which lets you change your system as it's running; a lot of people don't like that, and we can agree to disagree. :)
Anyway, all this combines to a system where the natural way to write a concurrent system is to have one process per session, and write the process to just listen for messages either from the client or from the rest of the system and process them in a straight forward way. And if you've got millions of connections, you can have millions of processes. Each process tends to be small and easy to understand, but sometimes the interaction between the processes can get a bit tricky to understand, because it just kind of arises out of the message passing.