You can either do this with a correlated subquery in the select list, or with a subquery in the from list joined back to you main table.
Correlated subquery:
SELECT DISTINCT m1.conversation_id, (SELECT * FROM messages m2 WHERE m2.conversation_id=m1.conversation_id ORDER BY created_at DESC LIMIT 1)
FROM messages m1
Subquery in the from clause:
SELECT m1.*
FROM messages as m1
JOIN (
SELECT conversation_id, max(created_at) AS max_created_at
FROM messages
GROUP BY conversation_id
) m2 on m1.conversation_id = m2.conversation_id and m1.created_at = m2.max_created_at
The difference between the 2 approach is that the correlated subquery will always return 1 records per conversation id. The second one may return more than 1 record per conversation, if 2 records have the same created_at value. If there is an auto incremented id field identifying each message uniquely, then you can use the max of this field instead of the created at field to go around this potential issue.