Я бы еще понял, если вопрос был string vs []byte.
В go строка это последовательность байт, если упрощенно - тот же []byte, только неизменямый. Строка может быть правильной utf8 последовательностью а может и не быть. Строковый литерал без управляющих последовательностей, объявленный в исходном коде, например, "строка" будет в utf8, т.к. исходный код в uft8.
rune (алиас для int32) хранит кодовую позицию символа. Нужно понимать, что преобразование []rune -> string или []rune -> []byte можно выполнить всегда, наоборот нет. Так же нужно понимать, что представление в памяти для []rune отличается от []byte или string и преобразования в []rune и обратно несколько дороже, чем копирование памяти.
покурить можно вот эту статью https://blog.golang.org/strings
Если же говорить о string vs []byte в контексте форматирования строки, как уже заметили выше, использование []byte удобно, если нужно передавать данные во вне. Интерфейсы для работы с сетью, файлами, шифрования и т.п. работают с []byte. fmt.Sprintf все равно форматирует в []byte и затем уже приводит []byte к string. Так зачем лишние преобразования []byte -> string -> []byte.
Еще один момент - оптимизация. Форматировать можно в выделенный заранее буфер ([]byte или в виде обертки bytes.Buffer), который многократно переиспользуется.
не все символы можно использовать в url
попробуйте
buffer.WriteString(url.QueryEscape(msg))
с параметрами лучше работать с помощью Values