Enviando newsletters con PHP.

La siguiente porción de código puede resultar muy útil para aplicarse en casos específicos en donde necesitemos ejecutar un script PHP más de una vez cada X minutos.

<?php
/**
 * Run this script 5 more times.
 */
if (++$count  <= 5) {
     echo "Execution number: $count\n";
     sleep(1);
     include __FILE__;
 }
 
 echo "Finished!\n";
?>

La solución sirve para aplicarse en casos en los que básicamente sólo utilizar cron no es suficiente.

Supongamos que estamos utilizando una librería como Swift Mailer para envíar newsletters personalizados semanales a los 6.000 usuarios del sitio que estamos desarrollando.

Nuestro código toma 25 usuarios por vez, abre una conección SMTP con Swift, realiza la composición del newsletter y efectua el envío correspondiente del email para cada usuario.

El archivo crontab de nuestro usuario en el servidor sería muy similar al siguiente:

*/5 * * * 1 /usr/bin/php $HOME/newsletter-sender.php >> $LOGFILE

Este script se ejecutaría entonces 12 veces por hora (60/5 minutos).

25 usuarios * 12 * 24 horas = 7.200 newsletters por día.

Como está indicado en la línea del archivo crontab, este script se ejecuta cada 5 minutos todos los días Lunes.

Pasado un tiempo la popularidad de nuestro sitio crece y la cantidad de usuarios registrados se incrementa a 35.000, por decir un número, y nos encontramos con el problema de que un solo día ya no es suficiente para enviar los ya populares newsletters de la manera que lo veníamos haciendo.

Además de este problema, nuestro cliente quiere que todos emails se entreguen a los sumo en 48 horas, por lo que modificar nuestra linea en el archivo crontab de nuestro usuario para hacer que el script se ejecute también durante los días Martes, Miércoles y Jueves ya no es una opción.

También podríamos duplicar la cantidad de envíos por sesión SMTP, pero esto no es recomendable ya que Yahoo! Mail y amigos recomiendan que no se les envíen más de X emails por sesión SMTP. De no respetar estas reglas sus emails pueden ser diferidos por tiempo indeterminado, ser marcados como SPAM o, peor aún, no ser entregados sin emitir ningún tipo de error.

Ejecutar el script 1 vez por minuto es una solución viable pero si la popularidad del sitio sigue (afortunadamente) creciendo, al poco tiempo vamos a tener que volver a lidiar con el mismo problema.

*/1 * * * 1,2 $HOME/newsletter-sender.php >> $LOGFILE

25 * 60 * 24 = 36.000 newsletters por día.

Es entonces donde resulta útil aplicar este tipo de soluciones. En vez de sólo una nuestro script puede repetir estos procesos varias veces cada vez que lo ejecutemos.

#! /usr/bin/php
<?php
require_once 'config.php';
/**
 * Code to generate newsletters.
 */
 
/**
 * Run this script 5 more times.
 */
if (++$count  <= 5) {
     echo "Execution number: $count\n";
     sleep(1);
     include __FILE__;
 }
 
 echo "\n--------------------------------------------------\n";
 echo "Finished!\n";
?>

Incluso no es estrictamente necesario correr nuestros scripts con vixie-cron. Podríamos, por dar un ejemplo, utilizar las características de daemontools y aprovechar sus propiedades para correr nuestros pequeños programas como pequeños daemons en entornos *nix.

Aclaro que, en este caso, el orden es importante ya que incluír el archivo antes de incrementar el valor de $count haría que entremos en un bucle infinito. Ésto, dependiendo del caso, puede ser un bug (cron) o una funcionalidad (daemontools).

Es también muy importante ser respetuosos con la asignación y concatenación de valores a las variables que utilizemos ya que a partir de la primer iteración en la que el archivo se incluya a si mismo todas las variables van a estar seteadas y los resultados seguramente no sean los esperados. Un buen ejemplo de este comportamiento es ver como funciona la variable $count.

Para terminar me gustaría dejar en claro que esta es sólo una alternativa de muchas para resolver este tipo de problemas. Con muchas me refiero a que se puede utilizar otro paradigma de programación, orientado a objetos en vez de procedural, e incluso otro lenguaje de programación como ser Python que tiene incorporado en su núcleo las librerías necesarias para el envío de emails y no depende de librerías externas como el caso de PHP y Swift.

Comentarios:

  1. No seria conveniente mover el codigo que manda el newsletter a una funcion y correr la funcion en vez de re-incluir 36000 veces el mismo archivo ? Digo xq aparentemente se podria hacer y funcionaria igual ... reemplaza con un while o un for y listo ...

    Esta buena la idea de hacer el sleep() para "pausar" el tema y correr el cron una sola vez.
    Me acabo de recordar a mi mismo que set_time_limit() es infinito si se corre el script en consola, pero no es asi cuando lo ejecutas via web, por lo cual hay que usarlo si eso es lo que se quiere.

    Saludiiiiiitos

    Enviado por Coke el 04/08/2008 05:4:10 (#).

  2. Coke, no sé si sería más conveniente porque estarías encapsulando toda la funcionalidad del programa dentro de una función y, como siempre, eso depende de la aplicación.

    La idea era bajar una sola idea, la de correr el script PHP como un daemon, y terminé bajando como 5 :P

    Pero como dije en el artículo, alternativas hay muchas, ésta es una.

    Enviado por Juan el 04/08/2008 05:4:15 (#).

  3. Muy interesante y util para tiempos futuros...
    Espero llegar pronto ...
    Saludos

    Enviado por _Pe_ el 04/08/2008 07:4:40 (#).