Cómo administrar correos electrónicos automáticos enviados desde una aplicación web

12

Estoy diseñando una aplicación web y me pregunto cómo diseñar la arquitectura para administrar el envío de correos electrónicos automatizados.

Actualmente tengo esta característica integrada en mi aplicación web y los correos electrónicos se envían en función de las aportaciones / interacciones del usuario (como la creación de un nuevo usuario). El problema es que conectarse directamente a un servidor de correo tarda un par de segundos. Escalando mi aplicación, este será un cuello de botella significativo en el futuro.

¿Cuál es la mejor manera de administrar el envío de una gran cantidad de correos electrónicos automatizados dentro de la arquitectura de mi sistema?

No habrá una gran cantidad de correos electrónicos enviados (2000 por día como máximo). Los correos electrónicos no necesitan enviarse de inmediato, hasta 10 minutos de retraso está bien.

Actualización: la cola de mensajes se ha dado como respuesta, pero ¿cómo se diseñaría? ¿Se manejará esto en la aplicación y se procesará durante un período de silencio, o necesito crear una nueva 'aplicación de correo' o servicio web para administrar la cola?

Gaz_Edge
fuente
¿Puedes darnos una idea aproximada de la escala? ¿Cientos, miles o millones de correos? Además, ¿deben enviarse los correos electrónicos de inmediato o es aceptable un pequeño retraso?
Yannis
El envío de correo electrónico implica la entrega de un mensaje SMTP a un servidor de correo receptor, pero eso no significa que el mensaje se haya entregado realmente. Así que, efectivamente, todo el envío de correos electrónicos es asíncrono, y no tiene sentido pretender "esperar al éxito".
Kilian Foth
1
No estoy "esperando el éxito", pero tengo que esperar a que el servidor SMTP acepte mi solicitud. @YannisRizos vea la actualización RE su comentario
Gaz_Edge
Para los correos de 2000 (que es el máximo descrito) simplemente funcionará. Cuando ocurren en digamos 10 horas hábiles, son 3 correos por minuto, lo cual es muy factible. Solo asegúrese de configurar bien su registro DNS y el proveedor acepta que los envíe en estas cantidades. También piense en: "¿cuál es el servidor de correo inactivo?". La carga de enviar 2000 correos no es algo de qué preocuparse.
Luc Franken
La respuesta a dónde está CRONTAB
Tulains Córdova

Respuestas:

15

El enfoque común, como ya mencionó Ozz , es una cola de mensajes . Desde una perspectiva de diseño, una cola de mensajes es esencialmente una cola FIFO , que es un tipo de datos bastante fundamental:

Cola FIFO

Lo que hace que una cola de mensajes sea especial es que, si bien su aplicación es responsable de la cola, un proceso diferente sería responsable de la cola. En la jerga de colas, su aplicación es el remitente de los mensajes, y el proceso de eliminación de colas es el receptor. La ventaja obvia es que todo el proceso es asíncrono, el receptor funciona independientemente del remitente, siempre que haya mensajes para procesar. La desventaja obvia es que necesita un componente adicional, el remitente, para que todo funcione.

Dado que su arquitectura ahora se basa en dos componentes que intercambian mensajes, puede usar el término elegante comunicación entre procesos .

¿Cómo afecta la introducción de una cola al diseño de su aplicación?

Ciertas acciones en su aplicación generan correos electrónicos. Introducir una cola de mensajes significaría que esas acciones ahora deberían enviar mensajes a la cola (y nada más). Esos mensajes deben contener la cantidad mínima absoluta de información necesaria para construir los correos electrónicos cuando su receptor los procese.

Formato y contenido de los mensajes.

El formato y el contenido de sus mensajes depende completamente de usted, pero debe tener en cuenta que cuanto más pequeños, mejor. Su cola debe ser tan rápida para escribir y procesar como sea posible, arrojar una gran cantidad de datos probablemente creará un cuello de botella.

Además, varios servicios de colas basados ​​en la nube tienen restricciones en el tamaño de los mensajes y pueden dividir mensajes más grandes. No lo notará, los mensajes divididos se servirán como uno solo cuando los solicite, pero se le cobrarán varios mensajes (suponiendo, por supuesto, que esté utilizando un servicio que requiere una tarifa).

Diseño del receptor.

Como estamos hablando de una aplicación web, un enfoque común para su receptor sería un simple script cron. Se ejecutará cada xminuto (o segundos) y:

  • nCantidad emergente de mensajes de la cola,
  • Procese los mensajes (es decir, envíe los correos electrónicos).

Tenga en cuenta que estoy diciendo pop en lugar de get o fetch, eso se debe a que su receptor no solo obtiene los elementos de la cola, sino que también los borra (es decir, los elimina de la cola o los marca como procesados). Cómo sucederá exactamente eso depende de su implementación de la cola de mensajes y de las necesidades específicas de su aplicación.

Por supuesto, lo que estoy describiendo es esencialmente una operación por lotes , la forma más simple de procesar una cola. Dependiendo de sus necesidades, es posible que desee procesar los mensajes de una manera más complicada (eso también requeriría una cola más complicada).

Tráfico

Su receptor podría tener en cuenta el tráfico y ajustar la cantidad de mensajes que procesa en función del tráfico en el momento en que se ejecuta. Un enfoque simplista sería predecir sus horas de alto tráfico en función de los datos de tráfico anteriores y suponiendo que utilizó un script cron que se ejecuta cada xminuto, podría hacer algo como esto:

if( 
    now() > 2pm && now() < 7pm
) {
    process(10);
} else {
    process(100);
}

function process(count) {
    for(i=0; i<=count; i++) {
        message = dequeue();
        mail(message)
    }
}

Un enfoque muy ingenuo y sucio, pero funciona. Si no lo hace, bueno, el otro enfoque sería encontrar el tráfico actual de su servidor en cada iteración y ajustar la cantidad de elementos del proceso en consecuencia. Sin embargo, no micro optimice si no es absolutamente necesario, ya que estaría perdiendo el tiempo.

Almacenamiento en cola

Si su aplicación ya usa una base de datos, entonces una sola tabla en ella sería la solución más simple:

CREATE TABLE message_queue (
  id int(11) NOT NULL AUTO_INCREMENT,
  timestamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  processed enum('0','1') NOT NULL DEFAULT '0',
  message varchar(255) NOT NULL,
  PRIMARY KEY (id),
  KEY timestamp (timestamp),
  KEY processed (processed)
) 

Realmente no es más complicado que eso. Por supuesto, puede hacerlo tan complicado como lo necesite, puede, por ejemplo, agregar un campo de prioridad (lo que significaría que esto ya no es una cola FIFO, pero si realmente lo necesita, ¿a quién le importa?). También podría simplificarlo, omitiendo el campo procesado (pero luego tendría que eliminar las filas después de procesarlas).

Una tabla de base de datos sería ideal para 2000 mensajes por día, pero probablemente no escalaría bien para millones de mensajes por día. Hay un millón de factores a considerar, todo en su infraestructura juega un papel en la escalabilidad general de su aplicación.

En cualquier caso, suponiendo que ya haya identificado la cola basada en la base de datos como un cuello de botella, el siguiente paso sería buscar un servicio basado en la nube. Amazon SQS es el único servicio que utilicé e hizo lo que promete. Estoy seguro de que hay bastantes servicios similares por ahí.

Las colas basadas en memoria también son algo a considerar, especialmente para las colas de corta duración. memcached es excelente como almacenamiento de cola de mensajes.

Sea cual sea el almacenamiento en el que decida construir su cola, sea inteligente y abstraiga. Ni su remitente ni su receptor deben estar vinculados a un almacenamiento específico, de lo contrario, cambiar a un almacenamiento diferente en un momento posterior sería un PITA completo.

Enfoque de la vida real

He creado una cola de mensajes para correos electrónicos que es muy similar a lo que está haciendo. Estaba en un proyecto PHP y lo he construido alrededor de Zend Queue , un componente de Zend Framework que ofrece varios adaptadores para diferentes almacenamientos. Mis almacenamientos donde:

  • Matrices PHP para pruebas unitarias,
  • Amazon SQS en producción,
  • MySQL en los entornos de desarrollo y prueba.

Mis mensajes eran tan simples como pueden ser, mi aplicación creó pequeños arreglos con la información esencial ( [user_id, reason]). El almacén de mensajes era una versión serializada de esa matriz (primero era el formato de serialización interno de PHP, luego JSON, no recuerdo por qué cambié). El reasones una constante y, por supuesto, tengo una gran tabla que asigna un lugar reasona explicaciones más completas (Me las arreglé para enviar cerca de 500 correos electrónicos a los clientes con el críptico reasonen lugar del mensaje más completo una vez).

Otras lecturas

Normas:

Herramientas:

Lecturas interesantes:

Yannis
fuente
Guau. ¡Es la mejor respuesta que he recibido aquí! No puedo agradecerles lo suficiente!
Gaz_Edge
Yo, y estoy seguro de que millones más usan este FIFO con Gmail y Google Apps Script. un filtro de Gmail etiqueta cualquier correo entrante según un criterio, y eso es todo, los pone en cola. Una secuencia de comandos de Google Apps se ejecuta cada X duración, recibe los primeros mensajes, los envía y los quita. Enjuague y repita.
DavChana
6

Necesita algún tipo de sistema de colas.

Una manera simple podría ser escribir en una tabla de base de datos y tener otras filas de proceso de aplicación externas en esta tabla, pero hay muchas otras tecnologías de colas que podría usar.

Podría tener una importancia en los correos electrónicos para que ciertos se activen casi de inmediato (por ejemplo, restablecimiento de contraseña), y los de menor importancia podrían agruparse para enviarse más tarde.

ozz
fuente
¿tiene un diagrama de arquitectura o un ejemplo que muestre cómo funciona esto? Por ejemplo, la cola se encuentra en una aplicación diferente, por ejemplo, la aplicación de correo, o obtiene el proceso desde dentro de la aplicación web durante un período de silencio. ¿O debería crear una especie de servicio web para procesarlos?
Gaz_Edge
1
@Gaz_Edge Su aplicación empuja elementos a la cola. Un proceso en segundo plano (un script cron muy probablemente) extrae x elementos de la cola cada n segundos y los procesa (en su caso, envía el correo electrónico). Una sola tabla de base de datos funciona bien como almacenamiento en cola para pequeñas cantidades de elementos, pero, en general, las operaciones de escritura en una base de datos son costosas y para cantidades más grandes es posible que desee ver servicios como SQS de Amazon .
Yannis
1
@Gaz_Edge No estoy seguro de poder diagramarlo más simple de lo que escribí "... escriba en una tabla de base de datos y tenga otras filas de proceso de aplicación externas en esta tabla ..." y para la tabla, lea "cualquier cola "cualquiera que sea la tecnología que pueda ser.
ozz
1
(continuación ...) Puede crear el proceso en segundo plano que borra la cola de una manera que tenga en cuenta su tráfico, por ejemplo, puede indicarle que procese menos elementos (o ninguno) en momentos en que su servidor está bajo estrés . Tendrá que predecir esos momentos estresantes mirando sus datos de tráfico anteriores (más fácil de lo que parece, pero con un gran margen de error) o haciendo que su proceso en segundo plano verifique el estado del tráfico cada vez que se ejecuta (más preciso, pero la sobrecarga añadida rara vez es necesaria).
Yannis
@YannisRizos ¿quieres combinar tus comentarios en una respuesta? Además, los diagramas y diseños de arquitectura serían útiles (¡estoy decidido a obtenerlos de esta pregunta esta vez! ;-))
Gaz_Edge
2

No habrá una gran cantidad de correos electrónicos enviados (2000 por día como máximo).

Además de la cola, lo segundo que debe considerar es enviar correos electrónicos a través de servicios especializados: MailChimp, por ejemplo (no estoy afiliado a este servicio). De lo contrario, muchos de los servicios de correo, como gmail, pronto enviarán sus cartas a una carpeta de correo no deseado.

ONZ_
fuente
2

He modelado el sistema de mi cola en diferentes 2 tablas como;

CREATE TABLE [dbo].[wMessages](
  [Id] [uniqueidentifier]  NOT NULL,
  [FromAddress] [nvarchar](255) NOT NULL,
  [FromDisplayName] [nvarchar](255) NULL,
  [ToAddress] [nvarchar](255) NOT NULL,
  [ToDisplayName] [nvarchar](255) NULL,
  [Graph] [xml] NOT NULL,
  [Priority] [int] NOT NULL,
  PRIMARY KEY CLUSTERED ( [Id] ASC ))

CREATE TABLE [dbo].[wMessageStates](
  [MessageId] [uniqueidentifier] NOT NULL,
  [Status] [int] NOT NULL,
  [LastChange] [datetimeoffset](7) NOT NULL,
  [SendAfter] [datetimeoffset](7) NULL,
  [SendBefore] [datetimeoffset](7) NULL,
  [DeleteAfter] [datetimeoffset](7) NULL,
  [SendDate] [datetimeoffset](7) NULL,
  PRIMARY KEY CLUSTERED ( [MessageId] ASC )) ON [PRIMARY]
) ON [PRIMARY]

Hay 1-1 relación entre estas tablas.

Tabla de mensajes para almacenar el contenido del mensaje. El contenido real (Para, CC, CCO, Asunto, Cuerpo, etc.) se serializa en el campo Gráfico en formato XML. Otra información Desde, Hasta solo se usa para informar problemas sin deserializar el gráfico. La separación de esta tabla permite dividir el contenido de la tabla en un almacenamiento de disco diferente. Una vez que esté listo para enviar un mensaje, debe leer toda la información, por lo tanto, no hay nada de malo en serializar todo el contenido en una columna con índice de clave principal.

Tabla MessageState para almacenar el estado del contenido del mensaje con información adicional basada en la fecha. La separación de esta tabla permite un mecanismo de acceso rápido con índices adicionales en el almacenamiento rápido de E / S. Otras columnas ya se explican por sí mismas.

Podría usar un grupo de subprocesos separado que escanee estas tablas. Si la aplicación y el grupo viven en la misma máquina, puede usar una clase EventWaitHandle para indicarle al grupo desde la aplicación que hay algo insertado en estas tablas; de lo contrario, es mejor escanear periódicamente con un tiempo de espera.

Ertan
fuente