Back-End

30 out, 2013

Enviando e-mail em massa rapidamente em PHP usando otimizações inteligentes

100 visualizações
Publicidade

Enviar mensagens de email para muitos destinatários é uma tarefa bem pesada. No entanto, usando otimizações inteligentes é possível enviar mensagens para milhões de usuários em um espaço tempo razoável, a partir do seu próprio servidor PHP, sem consumir demasiados recursos.

Leia este artigo para aprender como a classe de PHP “MIME E-mail Message Sending” tira proveito dessas otimizações inteligentes para enviar mensagens personalizadas para muitos destinatários de forma eficiente.

PHP e o mito da lentidão das linguagens interpretadas

O fato de que a maior parte dos aplicativos PHP rodam direto do código fonte PHP faz com que muitos desenvolvedores acreditem que o PHP é necessariamente lento porque precisa interpretar o código fonte para ser executado.

Desde o PHP 4.0, quando o Zend Engine 1 foi introduzido, o código fonte PHP não é interpretado durante a execução. Em vez disso, o PHP é compilado em byte code antes da execução, da mesma forma como fazem outras linguagens, tais como Java e C#. Apenas após o processo de compilação o PHP é executado pelo Zend Engine. Esse fato faz com que o código PHP rode muito mais rápido do que muitos desenvolvedores imaginam.

Entretanto, há um fator que faz com que o PHP execute ainda mais rápido para tarefas pesadas. Esse fator é que o PHP fornece muitas funções que podem processar grandes quantidades de dados com a velocidade próxima a linguagens de baixo nível, tais como C e C++.

Aliás, essas funções estão escritas em C, em vez de código PHP, que é compilado para byte code. O PHP age apenas como uma linguagem de cola que passa dados para essas funções C.

Esse fator torna o PHP adequado para realizar várias tarefas pesadas, como enviar mensagens de e-mail para muitos destinatários.

Além disso, o PHP possui várias funções embutidas por padrão especialmente para codificar dados para enviar mensagens de e-mail de forma eficiente.

Esta classe Mime message utiliza o máximo possível dessas funções para compor e enviar e-mails com grande eficiência.

Compondo e enviando mensagens de e-mail para muitos destinatários

Com frequência, aplicativos PHP precisam enviar mensagens para muitos usuários. Esse é o caso de sites que precisam enviar newsletters ou outros tipos de mensagens de massa.

Uma otimização que essa classe fornece a mensagens em massa é a função SetBulkMail. Ela auxilia a classe a se preparar para enviar muitas mensagens, uma após a outra.

O que essa função faz na prática depende na verdade da forma como a mensagem será enviada. A classe base e-mail_message_class utiliza a função mail() do PHP para entregar a mensagem. Nesse caso, o SetBulkMail não fará nada.

Entretanto, para sub-classes específicas como smtp_message_class, que utiliza servidores SMTP, ela evita que a classe feche a conexão com o servidor entre o envio das mensagens a dois destinatários. Isso evita a sobrecarga de fechar e reabrir a conexão SMTP com o servidor.

No caso do sendmail_message_class, ela ajusta a variável delivery_mode para um valor que diga ao servidor de e-mails sendmail para armazenar a mensagem em uma fila local em vez de esperar para entregar a mensagem para o servidor SMTP de destino.

No caso da qmail_message_class, a função SetBulkMail não faz diferença porque o qmail sempre armazena mensagens na fila local em vez de tentar enviá-la imediatamente para o servidor SMTP de destino.

Aqui está um código de exemplo para inicializar a classe sendmail_message_class em preparação para um envio em massa. Essa classe é apropriada para usar quando vocês estiver executando PHP em um servidor que possui Sendmail ou outro servidor de e-mail compatível, como Postfix, Qmail, Exim etc.

$message=new sendmail_message_class;
$message->SetBulkMail(true);

Otimização de newsletters com o mesmo corpo de mensagem para todos os destinatários

O caso mais simples de e-mail em massa são as newsletters que possuem o mesmo conteúdo para todos os destinatários, e apenas os cabeçalhos das mensagens variam de destinatário para destinatário.

A classe MIME message possui uma variável nomeada cache_body que quando definida como true diz à classe para armazenar em cache o corpo da mensagem montada quando enviá-la para o primeiro destinatário, portanto, evitando o desperdício de remontar a mensagem para os destinatários subsequentes.

Aqui está um loop de entrega de uma mensagem de exemplo para enviar mensagens com o mesmo body para muitos destinatários:

 $message->SetEncodedHeader('Subject', 'Some subject');

 /*
  * Define the recipients list
  */
 $to=array(
   array(
     "address"=>"peter@gabriel.org",
     "name"=>"Peter Gabriel"
   ),
   array(
     "address"=>"paul@simon.net",
     "name"=>"Paul Simon"
   ),
   array(
     "address"=>"mary@chain.com",
     "name"=>"Mary Chain"
   )
 );

 /*
  * Create a place holder text message body part
  */
 $text = 'Hello, some text message';
 $message->CreateQuotedPrintableTextPart($text, '',$text_part);

 /*
  * Create a place holder HTML message body part
  */
 $html = 'Hello, some HTML message';
 $message->CreateQuotedPrintableHtmlPart($html, '', $html_part);

 /*
  * Assemble the text and HTML parts as alternatives
  */
 $alternative_parts = array($text_part, $html);
 $message->AddAlternativeMultipart($alternative_parts);

 /*
  * Cache the message for all recipients
  */
 $message->cache_body = true;

 /*
  * Iterate for each recipient. 
  */
 foreach($to as $recipient)
 {

   /* Personalize the recipient address. */
   $message->SetEncodedEmailHeader('To',
     $recipient['address'], $recipient['name']);

   /* Send the message checking for eventually acumulated errors */
   $error=$message->Send();
   if(strlen($error))
     break;
 }

Enviando mensagens personalizadas para muitos destinatários

Enviar mensagens com conteúdo personalizado no corpo da mensagem é um pouco mais complexo, porque você precisa configurar diferentes dados de body para cada destinatário.

 /*
  * Create a place holder text message body part
  */
 $text_template = 'Hello {name}, some text message';
 $message->CreateQuotedPrintableTextPart($text_template, '',$text_part);

 /*
  * Create a place holder HTML message body part
  */
 $html_template =
   '<html><body>Hello {name}, some HTML message</body></html>';
 $message->CreateQuotedPrintableHtmlPart($html_template, '', $html_part);

 /*
  * Assemble the text and HTML parts as alternatives
  */
 $alternative_parts = array($text_part, $html);
 $message->AddAlternativeMultipart($alternative_parts);

 /*
  * Iterate for each recipient. 
  */
 foreach($to as $recipient)
 {

   /* Personalize the recipient address. */
   $message->SetEncodedEmailHeader('To',
     $recipient['address'], $recipient['name']);

   /* Create personalized body parts */
   $text = str_replace('{name}', $recipient['name'], $text_template);
   $message->CreateQuotedPrintableTextPart(
     $message->WrapText($text), '', $text_part);

   $html = str_replace('{name}', HtmlSpecialChars($recipient['name']),
     $html_template);
   $message->CreateQuotedPrintableHtmlPart($html, '', $html_part);

   /* Make the personalized parts replace the initially empty part */
   $message->ReplacePart($alternative_parts[0], $text_part);
   $message->ReplacePart($alternative_parts[1], $html_part);

   /* Send the message checking for eventually acumulated errors */
   $error=$message->Send();
   if(strlen($error))
     break;
 }

Otimizações para as últimas versões do PHP

Quando você quiser reduzir o tempo e o uso de CPU para enfileirar mensagens para uma campanha de envio em massa, é necessário otimizar o código que está dentro do loop de destinatário.

Nesse caso, o que toma mais tempo de CPU é a codificação das mensagens para formar o invólucro MIME das mensagens que são enviadas ao servidor de e-mail.

Para mensagens HTML padrão, com partes alternativas do body, como definido nos exemplos acima, o que toma mais tempo de CPU é codificar o corpo das mensagens utilizando o algoritmo de quoted-printable. Essa codificação é necessária para codificar o body da mensagem, para que elas não contenham caracteres especiais que são ilegais, uma vez que isso pode confundir gateways de servidores de e-mail.

Versões antigas do PHP não fornecem uma função no core para codificar mensagens de e-mail usando quoted-printable. Você poderia usar as funções imap_8bit para esse propósito, mas isso necessita que a extensão PHP IMAP esteja habilitada.

Consequentemente, a classe email_message_class fornece uma implementação em PHP puro do algoritmo de codificação quoted-printable. Essa não é uma solução muito rápida, especialmente quando você precisa personalizar as mensagens para muitos destinatários.

Desde o PHP 5.3, há uma função nomeada quoted_printable_encode para esse propósito. A email_message_class usa essa função nas versões do PHP que possuem suporte a ela, tornando muito mais rápido o envio de mensagens em massa.

Otimizando o processo de comunicação com o servidor de e-mail

A outra parte do loop de entrega que leva mais tempo é a comunicação com o servidor de e-mail. Nesse caso, não há muito mais a fazer além do que a classe já faz.

De qualquer forma, se possível, utilize a sub-classe de entrega especializada que usa o sendmail ou equivalente, como Postfix, Qmail, Exim. Isso é na maior parte das vezes mais rápido do que tentar enfileirar as mensagens no servidor de e-mail via protocolo SMTP.

Isso porque os servidores SMTP frequentemente usam um programa equivalente ao sendmail para armazenar as mensagens em filas, para que você não tenha que acrescentar um overhead através do protocolo de comunicação TCP/IP para servir o processo de comunicação.

Percebi que alguns desenvolvedores acreditam que enfileirar via SMTP é mais rápido do que enviar mensagens via sendmail. Esse talvez seja o caso quando o sendmail é configurado para tentar enviar a mensagem imediatamente ao servidor SMTP.

Como mencionado acima, quando você usa a função SetBulkMail, a sendmail_message_class passará parâmetros ao sendmail, para que ele apenas armazene a mensagem na fila do servidor de e-mail em vez de tentar enviar imediatamente a mensagem.

Outro aspecto é enviar mensagens ao servidor SMTP remoto de provedores de serviço de e-mail. Isso torna a entrega ainda mais lenta por conta do overhead de se comunicar com os servidores SMTP remotos que estão em data centers diferentes.

APIs de serviços de e-mail na nuvem

Não cobri neste artigo as soluções baseadas em APIs de serviços de e-mail em nuvem. Esses serviços normalmente são recomendados apenas para enviar mensagens para um número pequeno de endereços.

O custo do envio de mensagens para muitos endereços é frequentemente muito alto. Dependendo do propósito do seu aplicativo, pode não valer a pena contratar esses serviços. Esse é o caso de classes PHP que enviam entre 3 e 5 milhões de newsletters e mensagens de alerta por mês aos inscritos no site.

É por isso que o pacote de envio de mensagens MIME não fornece suporte direto para essas APIs de serviços. Eu talvez inclua o suporte para algumas dessas APIs caso haja demanda suficiente dos usuários. Apenas me avise se você tiver interesse, para que eu possa avaliar a implementação de sub-classes que ofereçam suporte a essas APIs.

Conclusão

Como você deve ter lido até aqui, é possível implementar tarefas pesadas como envio de e-mails em PHP com grande eficiência.

A classe MIME message tem estado ativa e em desenvolvimento desde 1999, portanto está bem madura hoje. Ela implementa muitas otimizações inteligentes para fornecer um ganho de desempenho significante para aplicativos de e-mail em massa.

Este artigo fornece um guia de como tirar vantagem dessas otimizações para alcançar um desempenho máximo. Por favor, poste seus comentários para este artigo se algo precisar ficar mais claro.

***

Artigo traduzido pela Redação iMasters, com autorização do autor. Publicado originalmente em http://www.phpclasses.org/blog/package/9/post/3-Fast-PHP-Mass-Mailer-Smart–Optimizations.html