Quand une entreprise de télécommunications au Sénégal lance une promotion Flash avec un million de clients à notifier, ou qu'une banque au Cameroun doit envoyer simultanément des alertes de transaction à des dizaines de milliers d'utilisateurs, un simple appel fetch() en boucle ne suffit plus. Les erreurs s'accumulent, les rate limits explosent, les messages se perdent. La solution ? Une architecture de queue management robuste, conçue pour absorber les pics de charge tout en respectant scrupuleusement les limites de l'API WhatsApp. Ce guide vous explique comment implémenter cette architecture en production.
Pourquoi une queue est indispensable pour WhatsApp API
Sans queue, votre système d'envoi présente plusieurs vulnérabilités critiques :
Perte de messages : si votre serveur redémarre pendant un envoi en masse, les messages non encore traités sont perdus définitivement. Pas de traçabilité, pas de récupération possible.
Dépassement du rate limit : l'API WhatsApp impose des limites de débit (80 messages/seconde maximum, quotas journaliers par tier). Sans throttling côté client, vous recevrez des erreurs 130429 en cascade.
Absence de retry automatique : en cas d'erreur temporaire (API Meta momentanément indisponible, timeout réseau), un envoi direct échoue sans relance automatique.
Pas de priorité : un message transactionnel urgent (alerte de sécurité, confirmation de paiement) peut se retrouver bloqué derrière 10 000 messages marketing.
Une queue résout tous ces problèmes. Elle persiste les messages, les traite à un rythme contrôlé, relance automatiquement les échecs et permet une gestion fine des priorités.
Architecture avec Bull et Redis
Bull est la bibliothèque de queue la plus mature pour Node.js. Elle s'appuie sur Redis pour la persistance et offre un tableau de bord de monitoring (Bull Board) très pratique.
Installation et configuration :
npm install bull ioredis @bull-board/express @bull-board/ui
// queue-config.js
const Queue = require('bull');
// Créer des queues avec différentes priorités
const queues = {
// Messages transactionnels : haute priorité
transactional: new Queue('whatsapp-transactional', {
redis: { host: process.env.REDIS_HOST, port: 6379 },
defaultJobOptions: {
attempts: 5,
backoff: { type: 'exponential', delay: 2000 },
removeOnComplete: 100, // Garder les 100 derniers succès
removeOnFail: 500 // Garder les 500 derniers échecs pour analyse
}
}),
// Messages marketing : priorité normale
marketing: new Queue('whatsapp-marketing', {
redis: { host: process.env.REDIS_HOST, port: 6379 },
limiter: {
max: 50, // Max 50 jobs
duration: 1000 // Par seconde
},
defaultJobOptions: {
attempts: 3,
backoff: { type: 'exponential', delay: 5000 },
removeOnComplete: 1000,
removeOnFail: 1000
}
}),
// Notifications de statut : basse priorité
status: new Queue('whatsapp-status', {
redis: { host: process.env.REDIS_HOST, port: 6379 }
})
};
module.exports = queues;
Processeurs de queue :
// queue-processors.js
const queues = require('./queue-config');
const { sendWhatsAppMessage } = require('./whatsapp-client');
// Processeur pour les messages transactionnels (5 workers)
queues.transactional.process(5, async (job) => {
const { to, type, payload, metadata } = job.data;
job.progress(10); // Début du traitement
try {
const result = await sendWhatsAppMessage(to, type, payload);
job.progress(100);
// Logger le succès
await logMessageEvent({
jobId: job.id,
messageId: result.messages?.[0]?.id,
phone: to,
type,
status: 'sent',
campaignId: metadata?.campaignId
});
return { messageId: result.messages?.[0]?.id, status: 'sent' };
} catch (error) {
const apiError = error.response?.data?.error;
// Classifier l'erreur pour décider si retry utile
if (isNonRetryableError(apiError?.code)) {
// Marquer comme failed définitivement sans retry
job.discard();
await logMessageEvent({
jobId: job.id,
phone: to,
type,
status: 'failed',
errorCode: apiError?.code,
errorMessage: apiError?.message
});
}
throw error; // Bull va gérer le retry selon la config
}
});
function isNonRetryableError(code) {
const permanentErrors = [
131030, // Numéro invalide
131031, // Client a bloqué
132000, // Template introuvable
190, // Token invalide
10, // Permission refusée
200 // Permission manquante
];
return permanentErrors.includes(code);
}
// Processeur pour les campagnes marketing (2 workers, plus lent)
queues.marketing.process(2, async (job) => {
const { to, templateName, params, metadata } = job.data;
// Vérifier l'opt-in avant envoi
const hasOptIn = await checkOptIn(to);
if (!hasOptIn) {
job.discard();
return { status: 'skipped', reason: 'no_opt_in' };
}
const result = await sendWhatsAppMessage(to, 'template', {
name: templateName,
language: { code: 'fr' },
components: buildTemplateComponents(params)
});
return { messageId: result.messages?.[0]?.id, status: 'sent' };
});
Gestion des campagnes en masse
Pour une campagne ciblant des milliers de contacts, ne chargez pas tous les contacts en mémoire. Utilisez une approche par lots (batch processing) :
// campaign-scheduler.js
async function scheduleCampaign(campaignId, templateName, params) {
const BATCH_SIZE = 500; // Traiter 500 contacts à la fois
let offset = 0;
let totalQueued = 0;
console.log(`Démarrage campagne ${campaignId}`);
while (true) {
// Charger un batch de contacts depuis la base de données
const contacts = await db.query(
`SELECT phone, name, custom_fields
FROM campaign_contacts
WHERE campaign_id = $1 AND status = 'pending'
ORDER BY id
LIMIT $2 OFFSET $3`,
[campaignId, BATCH_SIZE, offset]
);
if (contacts.rows.length === 0) break; // Plus de contacts
// Créer les jobs Bull en batch
const jobs = contacts.rows.map((contact, index) => ({
data: {
to: contact.phone,
templateName,
params: personalize(params, contact),
metadata: { campaignId, contactId: contact.id }
},
opts: {
delay: index * 15, // Étaler l'envoi : 1 message toutes les 15ms ≈ 67/sec
jobId: `campaign:${campaignId}:${contact.id}` // ID unique pour déduplication
}
}));
await queues.marketing.addBulk(jobs);
totalQueued += contacts.rows.length;
offset += BATCH_SIZE;
console.log(`${totalQueued} messages ajoutés à la queue`);
// Pause entre les batches pour ne pas surcharger Redis
await sleep(100);
}
await updateCampaignStatus(campaignId, 'queued', totalQueued);
console.log(`Campagne ${campaignId} : ${totalQueued} messages programmés`);
}
function personalize(params, contact) {
return params.map(param =>
param.replace('{{name}}', contact.name || 'cher client')
);
}
Monitoring de la queue
Bull Board fournit une interface web pour visualiser l'état de vos queues en temps réel :
// dashboard.js
const express = require('express');
const { createBullBoard } = require('@bull-board/api');
const { BullAdapter } = require('@bull-board/api/bullAdapter');
const { ExpressAdapter } = require('@bull-board/express');
const queues = require('./queue-config');
const serverAdapter = new ExpressAdapter();
serverAdapter.setBasePath('/admin/queues');
createBullBoard({
queues: Object.values(queues).map(q => new BullAdapter(q)),
serverAdapter
});
const app = express();
app.use('/admin/queues', serverAdapter.getRouter());
app.listen(3001, () => console.log('Bull Board accessible sur http://localhost:3001/admin/queues'));
Pour les grandes campagnes, combinez ce système de queue avec la compréhension des limites de rate limiting WhatsApp et les stratégies d'automatisation WhatsApp.
FAQ
Q: Redis est-il obligatoire pour implémenter une queue WhatsApp ?
R: Non, mais c'est fortement recommandé pour la persistance. Des alternatives existent : BullMQ avec PostgreSQL (via @taskforcesh/bullmq-pro), des services managés comme AWS SQS ou Google Cloud Tasks. Pour les projets simples avec peu de volume, une queue en mémoire avec gestion manuelle des retries peut suffire, mais sans persistance.
Q: Comment estimer la taille de la queue nécessaire pour une campagne de 50 000 messages ?
R: À 50 messages/seconde (safe pour la plupart des comptes), 50 000 messages prennent 1000 secondes soit environ 17 minutes. La queue Redis stocke chaque job en mémoire (environ 500 bytes par job) soit 25 MB pour 50 000 jobs — tout à fait raisonnable. Prévoyez 1 Go de RAM Redis pour des campagnes jusqu'à 500 000 messages.
Q: Comment gérer les jobs en échec après toutes les tentatives de retry ?
R: Les jobs échoués définitivement (après tous les retries) sont déplacés dans la "failed queue" de Bull. Configurez un worker périodique qui analyse ces jobs, identifie les patterns d'erreur, et traite les cas récupérables (ex : numéros à mettre à jour en base de données, contacts à retirer des listes). Une alerte doit être déclenchée si le taux d'échec dépasse un seuil.
Q: Peut-on mettre en pause une campagne en cours sans perdre les messages non encore envoyés ?
R: Oui, c'est l'un des grands avantages des queues. Bull permet de mettre en pause une queue avec queue.pause(). Les jobs en attente restent dans Redis et reprennent quand vous appelez queue.resume(). Vous pouvez même retirer des jobs spécifiques avec job.remove() si vous devez exclure des contacts en cours de campagne.
Q: Comment gérer les priorités entre messages transactionnels et marketing pendant les pics ?
R: Utilisez des queues séparées avec des workers dédiés. Allouez 5 workers aux transactionnels et 2 aux marketing, par exemple. Pendant un pic de messages transactionnels, les workers dédiés continuent à traiter sans être ralentis par les messages marketing. BullMQ Pro propose une gestion native des priorités inter-queues si vous avez besoin d'une solution plus fine.
Envoyez des milliers de messages WhatsApp en toute fiabilité avec Whakup. Notre infrastructure gère automatiquement les queues, les retries et le rate limiting pour vous.

Co-fondateur et Chief Product Officer de Whakup, Pablo conçoit les fonctionnalités qui permettent aux marques africaines de maximiser leur impact sur WhatsApp.
Prêt à passer à l'action ?
Essayez Whakup gratuitement pendant 15 jours. Aucune carte bancaire requise.
Démarrer l'essai gratuitArticles similaires
Bulk messaging WhatsApp API : envoi en masse performant et conforme
Maîtrisez l'envoi en masse sur WhatsApp Business API. Architecture, conformité Meta, optimisation des taux de livraison et stratégies pour des campagnes à grande échelle en Afrique.
Contacts et vCards dans WhatsApp Business API : partage professionnel
Comment partager des contacts et vCards via l'API WhatsApp Business Cloud. Format, implémentation et cas d'usage professionnels pour les entreprises africaines.
Location messages WhatsApp : partager une adresse ou position GPS
Comment envoyer et recevoir des messages de localisation via WhatsApp Business API. Partage d'adresse, coordonnées GPS, cas d'usage livraison et points de vente.