¿Te ha pasado? Ese correo que no llega, revisas la carpeta de SPAM y nada, oh acá está, llegó, uff, finalmente… hagamos click sobre el link…, ¿what? ¿cómo que “token expirado”? ¿tengo que volver a pedir otro mail? Qué frustración 😖.
Todos los correos son asíncronos, pero no todos pueden tener la misma prioridad. Correos de confirmación de cuenta, invitación a registrarte, el código para poder hacer la transferencia o el magic link para hacer login deberían siempre tener mayor prioridad que correos de notificación o newsletters.
Hoy aprendí que no hay forma en Rails de especificar algo tan obvio como esto 🤦🏻♀️ — o al menos yo no la encuentro ni Googleando — .
Entiéndase: ActionMailer permite especificar el nombre de la cola donde se almacenarán todos los correos que se quieran enviar más tarde — mediante el uso de algún sistema que soporte ActiveJob, dígase delayed_job, Resque, o Sidekiq y que por cierto es claramente una buena práctica — pero esto también significa que cada correo tendrá la misma prioridad.
Esto no es un problema si tu sistema envía unos pocos mails diarios, pero en el caso de Get on Board, donde todos los miércoles y domingos enviamos un digest personalizado a cada uno de nuestros profesionales, la cola crece inmediatamente a decenas de miles de correos encolados.
Imagina qué pasa si alguno de estos profesionales, mientras se procesa la cola, pide un magic link para poder iniciar sesión en Get on Board 😬 — y nos ha pasado, dicho correo tendría que esperar su turno que puede llegar a ser horas.
Lo que yo propongo es que debería existir la forma de hacer algo así:
class NormalMailer < ApplicationMailer queue :mailers def welcome(user) # send mail from our CEO welcoming the user end end class PriorityMailer < ApplicationMailer queue :critical def magic_link(user) # send mail with the authentication token end end
Luego en el caso de sidekiq, bastaría especificar el orden de prioridad:
# sidekiq.yml --- :timeout: 5 :concurrency: <%= ENV.fetch('SIDEKIQ_MAX_THREADS') { 5 } %> :queues: # In order of priority - [critical, 2] - mailers
Workaround
Nuestra solución in-house es crear worker proxies que escuchan en colas con mayor prioridad y usan deliver_now para evitar encolar el mail:
module MailWorker class CriticalMail include Sidekiq::Worker sidekiq_options queue: :critical end class SendMagicLink < CriticalMail def perform(args) WebproMailer.magic_link(*args).deliver_now end end end
Si has pasado por esto y encontraste una mejor solución, déjanos un comentario o escríbenos directamente a team at getonbrd.com contándonos 🤗.