When implementing dapps - always use internal messages only.
Never use external messages. You can safely ignore the fact that external messages exist.
Wallets on TON are smart contracts too
Unlike most blockchains, Ethereum included, when a user on TON downloads a wallet app, this wallet app will deploy a wallet smart contract for the user. This wallet smart contract holds the user's TON coin balance.
Every interaction that the user does takes place through this wallet smart contract. The wallet smart contract is what holds the user's TON coin balance, so any action like sending a transaction to a dapp contract must go through the wallet smart contract to pay for gas from this balance.
Read this to learn more about wallet smart contracts on TON.
Message and TON contracts
To communicate with a smart contract on TON, you must send it a message. Every transaction is essentially a message. The main entry point of a smart contract is the message handler.
These are messages sent by other smart contracts - particularly messages sent by wallet smart contracts. Since we said above that every user interaction with a dapp would go through their wallet smart contract (to pay for gas), this means every user interaction with a dapp will come in the form on an internal message.
To understand why these messages exist we must ask ourselves how are wallet smart contracts implemented. You can actually implement a wallet smart contract by yourself! Here is an example of the latest code today for the default wallet. You would normally never have to do this though! This task is normally reserved to the TON core team or projects explicitly developing wallets.
As you probably guessed, users communicate with their wallet smart contracts using an external message. These messages are designed for external entities that are not smart contracts, like a wallet app. But.. since every such action must go through a wallet contract to pay for gas, your dapp will eventually receive its messages from a smart contract as internal messages.
The standard message flow
Let's assume that the user is trying to transfer a jetton token to another user. This interaction requires sending a transaction to the jetton smart contract (a dapp). The flow would be:
user ---(external msg)--->
user's wallet smart contract ---(internal msg)--->
jetton smart contract
If the user is using TonKeeper wallet for example, the external message would be encoded by the TonKeeper mobile app.
Why did I see some dapps handling external messages?
It is possible for a dapp contract to handle external messages. I saw some developers are using this approach for deployment and some developers are using this for admin role actions like upgrades.
In both cases though, the best practice is to use internal messages and not external messages. An admin role can be a multi-sig contract too, by using an external message they've broken the abstraction and will not be able to support multi-sig.