Push notifications são um dos canais mais eficazes para engajar usuários em tempo real. No entanto, quando falamos de grandes volumes (dezenas ou até centenas de milhares de notificações), surgem desafios técnicos: latência, bloqueios de rede, falhas intermitentes e gerenciamento de filas.
Neste artigo, vamos mostrar como estruturar um pipeline robusto em Laravel + PHP para enviar notificações em massa sem sobrecarregar o servidor, usando o Apple Push Notification Service (APNs) como exemplo.
Desafios do Envio em Massa
Enviar grandes volumes não é o mesmo que disparar algumas centenas.
Problemas comuns incluem:
- Overhead de conexões: abrir e fechar conexões TLS a cada envio gera lentidão.
- Falhas 429 (Too Many Requests): o servidor da Apple bloqueia excesso de requisições simultâneas.
- Tokens inválidos: dispositivos desinstalam apps e os tokens ficam “sujos”.
- Memória: carregar todos os registros da base de uma vez pode travar o processo.
Estrutura da Base de Dados
Imagine uma tabela app_push
que armazena dispositivos e plataformas:
CREATE TABLE app_push (
id INT AUTO_INCREMENT PRIMARY KEY,
device_token VARCHAR(255) NOT NULL,
plataforma ENUM('iOS','Android') NOT NULL,
filial VARCHAR(50) NOT NULL
);
E uma fila app_push_fila
para armazenar os envios pendentes:
CREATE TABLE app_push_fila (
id INT AUTO_INCREMENT PRIMARY KEY,
device_token VARCHAR(255) NOT NULL,
plataforma ENUM('iOS','Android') NOT NULL,
titulo VARCHAR(255),
mensagem TEXT,
status ENUM('Pendente','Enviado','Erro') DEFAULT 'Pendente'
);
Código PHP/Laravel para Alta Escala
A chave para lidar com grandes volumes está em reusar a conexão HTTP/2 com o APNs, em vez de criar uma conexão por notificação.
public function enviarPushs()
{
$http2ch = curl_init();
$this->configurarConexao($http2ch);
DB::table('app_push_fila')
->where('status', 'Pendente')
->orderBy('id')
->chunk(2000, function ($notificacoes) use ($http2ch) {
foreach ($notificacoes as $n) {
$payload = [
'aps' => [
'alert' => [
'title' => $n->titulo,
'body' => $n->mensagem,
],
'sound' => 'default',
]
];
$url = "https://api.push.apple.com/3/device/{$n->device_token}";
curl_setopt($http2ch, CURLOPT_URL, $url);
curl_setopt($http2ch, CURLOPT_POSTFIELDS, json_encode($payload));
$result = curl_exec($http2ch);
$status = curl_getinfo($http2ch, CURLINFO_HTTP_CODE);
if ($status == 200) {
DB::table('app_push_fila')->where('id', $n->id)->update(['status' => 'Enviado']);
} else {
DB::table('app_push_fila')->where('id', $n->id)->update(['status' => 'Erro']);
}
}
});
curl_close($http2ch);
}
Boas Práticas
- Chunking: use
chunk(2000)
ou similar para processar em blocos e não travar a memória. - Retries: implemente tentativas com backoff exponencial para erros
429/5xx
. - Remoção de tokens inválidos (
410 Unregistered
no APNs). - Collapse ID: agrupe notificações do mesmo tipo para reduzir volume entregue.
- Monitoramento: logue sempre os retornos da Apple/Google para entender falhas.
Conclusão
Com pequenas mudanças de arquitetura — como reusar conexões e processar em blocos — você pode transformar um processo pesado e instável em uma rotina escalável e confiável, capaz de enviar 100.000+ push notifications em minutos.
Assim, seu app garante engajamento em tempo real, mesmo em cenários de alta demanda.