Disclaimer: I don’t actually know, but my understanding (based on reading the docs) is:
It depends on the PacketType specified when opening the socket. Erlang’s inet module (which underlies gen_tcp) has a support for a few different packet types. They are described here: Erlang -- inet. If the protocol you’re implementing is compatible with one if those supported by inet, each message you receive in your mailbox will all be a complete packet.
If the protocol you’re implementing is not supported by inet, you will use packet type raw. In this case, I believe the number of bytes you get will depend on the OS directly - inet will not try to package up packets for you, but just send you a message every time it receives bytes from the OS kernel. The exception being, if your socket transfers to passive mode, inet will buffer bytes until you go back to active, in which case I expect you will get all the buffered bytes at once. There is some mention in the inet docs about raw socket options you can pass to the kernel which might affect the behavior, but these are non-portable (see the Example section on the page I linked above).