Todos los mails son iguales… pero algunos son más iguales que otros

Por

¿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 🤗.

Lo más reciente en Blog