Este caso é exatamente para o que os loops são bons (e projetados).
Como você faz coisas que fogem do escopo do banco de dados, é perfeitamente legítimo usar loops para elas.
Os bancos de dados são projetados para armazenar dados e realizar as consultas nesses dados que os retornam da maneira mais prática.
Bancos de dados relacionais podem retornar dados na forma de conjuntos de linhas.
Cursores (e loops que os usam) são projetados para manter um conjunto de linhas estável para que algumas coisas com cada uma de suas linhas possam ser feitas.
Por "coisas" não quero dizer truques de banco de dados puros, mas coisas reais que afetam o mundo exterior, as coisas para as quais o banco de dados foi projetado, seja exibir uma tabela em uma página da Web, gerar um relatório financeiro ou enviar um e-mail.
É ruim usar cursores para tarefas de banco de dados puras (como transformar um conjunto de linhas em outro), mas é perfeitamente bom usá-los para coisas como aquela que você descreveu.
Os métodos baseados em conjunto são projetados para funcionar em uma única transação.
Se sua consulta set-base falhar por algum motivo, seu banco de dados reverterá para o estado anterior, mas você não poderá "reverter" um email enviado. Você não poderá acompanhar suas mensagens em caso de erro.