Envío de correos masivos en Symfony2 mediante spool y envío inmediato

symfony2_mailing

En uno de mis últimos proyectos, me he visto en la necesidad de realizar un envío masivo de correos y como el código del CMS es reutilizable, me he planteado utilizar el envío de correo mediante spool. Os detallo mi experiencia a continuación.

¿Por qué realizar un envío por spooling?

Dependiendo el método que configuremos en nuestro proyecto para envío de correos, esta tarea puede ser bastante lenta. Por ejemplo, en el caso que decidamos la entrega mediante SMTP, nuestro proceso deberá conectarse al servidor STMP, realizar las cadenas de saludo del protocolo, enviar el correo y cerrar la conexión. Este procedimiento para el envío de correos individuales es la forma más sencilla, pero en el caso que nuestro procedimiento tenga que enviar muchos correos lo más probable es que nuestro script finalice con un timeout, y debamos empezar de nuevo el envío con lo que probablemente los primeros destinatarios del correo lo reciban duplicado.

Si configuramos el método de generación por spool, todos nuestros correos se guardaran en un espacio físico, y posteriormente, desde la línea de comandos se enviarán de forma programada sin necesidad de colapsar el servidor.

Primer paso, configurando el método spooling

Cambiar el método de transporte de inmediato a spool es tan sencillo como en el fichero config.yml de nuestro proyecto cambiar las líneas:

# app/config/config.yml
spool:
   type: file
   path: "%kernel.root_dir%/../var/spool"

En mi caso, me gusta tener una carpeta “var” en la raíz del proyecto donde guardo las sesiones u otros datos temporales ya que por defecto todo se guarda en la carpeta caché y si por cualquier motivo limpiamos la caché se eliminan las sesiones autentificadas o los correos pendientes de enviar. Recuerda darle permisos de escritura a la carpeta var, de igual forma que haces a las carpetas app/cache y app/logs, así mismo ignorar la carpeta en nuestro sistema de versionado de ficheros.

Si enviamos un correo, veremos que se crea un fichero de texto con extensión .message en la carpeta /var/spool

En segundo paso es configurar una tarea programa/cron que se encargue de enviar los correos. Deberemos adaptar nuestra linea de comandos de consola del proyecto según nuestro sistema operativo y ubicación de la carpeta raíz:

app/console swiftmailer:spool:send --message-limit=50 --env=prod > /dev/null 2>>app/logs/mail_error.log 

Controlador de envío masivo

En nuestro backend disponemos de un formulario de envío masivo a los usuarios registrados según diferentes criterios y tablas, un ejemplo sería:

$to= array(
           array('email'=>'pepe@gmail.com','nombre'=>'Pepe Gmail'),
           array('email'=>'lucas@hotmail.com','nombre'=>'Lucas Hotmail')
          );

foreach ($to as $p)
       {
           $message = \Swift_Message::newInstance()
               ->setSubject('Newsletter Symfony2')
               ->setFrom('webmaster@hotmail.com')
               ->setBody('Texto del mensaje')
               ->setTo(array($p['email'] => $p['nombre']));

           $this->get('mailer')->send($message);

       }

Llegado a este punto, nuestro controlador generará todos los correos individuales, y serán enviados en grupos de 50 cada cada X minutos según hayamos programado el cron.

¿Que problema tenemos ahora? Pues si por ejemplo en nuestra página hay un formulario de registro o cualquier otro procedimiento que hay que enviar un correo de forma inmediata, este será puesto en el spool y procesado cuando se hayan finalizado los anteriores envíos, por lo que puede demorarse minutos o hasta horas. En symfony 1 swiftmailer tenía la función $this->getMailer()->sendNextImmediately()->send($message); pero en Symfony2, no está implementado, por lo que deberemos buscar un método nuevo.

Inyectando un nuevo transportador de correos para envío inmediato

La solución es sencilla (después de haber dedicado horas de búsqueda por Internet). En el bundle que deseamos enviar correos instantáneos inyectamos en el servicio la nueva configuración, para ello editamos su correspondiente fichero services.yml (o xml):

# src/PortadaBundle/Resources/config/services.yml
services:
    instant_mailer:
        class: %swiftmailer.class%
        arguments: ["@?swiftmailer.transport.real"]

Y en nuestro controlador del frontend, modificamos el envio de correo de esta manera:

if ($this->get('mailer')->getTransport() instanceof \Swift_Transport_SpoolTransport){
    $this->get('instant_mailer')->send($message);
} else {
    $this->get('mailer')->send($message);
}

El condicional que el transporte es instancia de \Swift_Transport_SpoolTransport permite que funcione correctamente tanto en entorno (dev o prod).

¿Tienes algún comentario? ¡no dudes en participar con tu opinión en este artículo, todos aprenderemos!

Anuncios

2 comentarios en “Envío de correos masivos en Symfony2 mediante spool y envío inmediato

  1. Pero si quiero agregar ademas de estos dos ejemplos un tercer spool que envie correos cada cierto tiempo digamos cada dos o tres dias o una semana depuues… ??

    • Hola Alejo, el periodo de envío (cada 5 minutos, 1 vez al día o a la semana) lo determinará los parámetros del CRON que configures en el servidor. El spool sólo gestiona la cola, si los correos corresponde a información diaria o semanal será tarea del controlador que hace generación de mensajes.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s