Antes de sair instalando módulo de SMTP: em Magento Open Source 2.4.7 e 2.4.8, o e-mail de pedido que “não sai” quase sempre está preso no envio assíncrono. O Magento marca o pedido para mandar depois, quem faz o depois é o cron, e cron parado é e-mail que nunca chega.
Olhe a configuração antes de tudo
sudo -u www-data bin/magento config:show sales_email/general/async_sending
sudo -u www-data bin/magento config:show system/smtp/disable
Se o primeiro devolve 1, o envio é assíncrono. Se o segundo devolve 1, a loja está com toda a comunicação por e-mail desligada e nem o assíncrono salva. Quando o comando responde que o valor não existe, vale o padrão do módulo, que é 0 nos dois casos.
A fila mora na sales_order
Com o assíncrono ativo, o pedido nasce com send_email = 1 e email_sent nulo. O cron varre essa coluna e envia em lote:
SELECT entity_id, increment_id, send_email, email_sent, created_at
FROM sales_order
ORDER BY entity_id DESC
LIMIT 10;
Uma coluna email_sent cheia de NULL confirma o diagnóstico. Quem faz esse trabalho é o job sales_send_order_emails, do grupo default, agendado de minuto em minuto pelo Magento_Sales.
O cron está mesmo rodando?
crontab -u www-data -l | grep magento
sudo -u www-data bin/magento cron:run --group=default
Se a primeira passada não mandar nada, rode de novo: uma execução gera o agendamento e a seguinte processa o que ficou pendente. Saiu o e-mail? Faltava o crontab do sistema, que se instala com bin/magento cron:install — sempre como o usuário do web server, porque bin/magento rodado como root deixa var/ e generated/ com dono errado e derruba a loja logo depois. Vale conferir a tabela cron_schedule atrás de linhas com status missed ou error nesse job.
Para tirar a dúvida rápido em homologação, desligue o assíncrono com bin/magento config:set sales_email/general/async_sending 0 seguido de cache:flush. Só lembre que isso vale para pedidos novos: os antigos continuam esperando o cron que você ainda precisa consertar.