Rails: Action Mailers

João Paulo Lethier
Zygo Tech
Published in
2 min readMar 16, 2017

Enviar email em um sistema rails é bem simples. Não leva mais do que alguns minutos para conseguir configurar o projeto utilizando o action mailer e as convenções do rails.

Apesar de toda essa facilidade, há cerca de três meses vínhamos tendo um problema que não conseguíamos resolver. Nós usamos o sparkpost para o envio dos emails, assim como enviamos a maioria dos emails em background, em jobs do sidekiq. Como é comum a toda base de dados, alguns usuários nossos possuem emails inválidos ou marcam como spam para não receber mais emails. Nesses casos, quando tentamos nos comunicar com eles, a gem do sparkpost joga uma exceção. O problema é que queríamos implementar algo que capturasse essa exceção e salvasse no nosso banco de dados que esse email foi rejeitado para não mandarmos novamente para esse remetente.

Uma alternativa para resolver isso seria implementarmos um begin rescue em todos os lugares que chamasse algum mailer no projeto. Isso de fato resolveria o nosso problema, pois poderíamos tratar sempre as exceções, só que isso adicionaria um outro problema, teríamos que repetir esse begin rescue em vários lugares no projeto e lembrar dele sempre que adicionássemos uma nova chamada a um mailer.

Pesquisando uma outra alternativa que não nos trouxesse esses fatores negativos para manutenção futura, descobri que no Rails 5 já era possível implementar umrescue_from SimpleSpark::Exceptions::Error do |error| no ApplicationMailer, funcionando de forma similar ao que o rails já permitia utilizarmos no ApplicationController em versões mais antigas.

class ApplicationMailer < ActionMailer::Base
rescue_from SimpleSpark::Exceptions::Error do |exception|
# besides the exception variable, you have here the message method that has all message attributes, so you can save that message.to(it is always an array) are invalid emails
message.to.each do |email|
EmailRejection.create(email: email)
end
end
end

Dessa forma, basta que todos os mailers do projeto herdem sempre do ApplicationMailer e automaticamente todos os emails enviados que o sparkpost jogar essa exceção já serão capturados e, nesse exemplo acima, salvos em na tabela email_rejections para que eu possa consultar essa tabela antes de enviar qualquer email e cancelar o envio quando o remetente for inválido.

Mas agora novamente temos o problema de verificar em vários lugares antes do envio para decidir se o email deve ou não ser enviado. Para isso o rails já possui uma solução bem fácil há algum tempo que são os interceptors(link em inglês).

# Prevent ActionMailer to send emails to people that had already reject
class PreventRejectedMailInterceptor
def self.delivering_email(message)
return unless EmailRejection.where(email: message.to).exists?
message.perform_deliveries = false
end
end

Basta criar uma classe como o exemplo acima e registrar o interceptor em um initializer da seguinte forma: ActionMailer::Base.register_interceptor(RedirectOutgoingMails).

Dessa forma conseguimos implementar toda a captura de exceções e a prevenção de envio para remetentes rejeitados sem precisarmos nos preocupar com isso em cada chamada de mailer feita dentro do projeto.

--

--