Skip to content

Glossary drawing · Reliability & scale

Message Queue

Also called: Job queue · Task queue · Message broker · Send queue

Note 1 —A durable buffer between your application and the WhatsApp Cloud API that absorbs send spikes, paces delivery to your throughput limit and survives restarts.

Term
Message Queue
Abbreviation
—
Category
Reliability & scale
Level
Foundation
Reviewed
Sheet no.
GLO-REL-654
Drawn by
InfiQ Glossary

Drawing 1 · Interactive

Can four workers keep up with a broadcast burst?

40× slow motionService time S = 50 ms per job on average · 4 workers

Queue diagram: 12 sends waiting, 4 of 4 workers busy, 7 done, 1 dead-letter queue.

Backlog
∞
grows without bound
Throughput
80/s
78.4/s done · 1.6/s dead-letter queue
Average wait
∞
keeps growing
Utilisation ρ
1.25
≈ 4 of 4 busy
100 sends/s
4

ρ = λ × S ÷ c = 100/s × 50 ms ÷ 4 = 1.25

Backlog grows without bound. ρ = 1.25 ≥ 1: sends arrive faster than 4 workers can finish them. Add workers, speed up each job or slow the producer.
Illustrative: four workers each spend 50 ms per send, so the pool tops out near 80 sends a second, one number's default throughput. A 100-per-second burst builds a backlog; drag the sliders to see what drains it.

Utilisation is at or above 1: the backlog grows without bound.

What is a message queue?

A message queue is a durable list of jobs that one part of a system writes and another reads later. Producers (your order service, campaign tool or chatbot) add a job such as "send template order_dispatched to this customer" and return immediately. Consumers, called workers, take jobs off the queue, call the Cloud API and acknowledge each job once it is done. If a worker crashes in the middle of a job, the unacknowledged job becomes visible again and another worker picks it up.

In a WhatsApp stack this buffer does three jobs. It absorbs spikes: a campaign that queues 200,000 messages in a few seconds does not need 200,000 simultaneous API calls. It paces: workers send at the number's Throughput ceiling (80 messages per second by default) instead of tripping error 130429. And it decouples: a slow CRM or a Graph API incident delays sends rather than failing the request that created them. The same pattern sits behind webhook receivers, which acknowledge Meta quickly and queue each event for processing, and it is the backbone of an Event-Driven Architecture.

A few mechanics matter. The visibility timeout (or lease) is how long a taken job stays hidden before it is redelivered, so it must be longer than your slowest send including retries. Acknowledging after the send, not before, gives at-least-once delivery, which means duplicates are possible and an Idempotency Key is needed. Jobs that keep failing move to a Dead-Letter Queue instead of blocking the line, and Consumer Lag (queue depth and the age of the oldest job) tells you whether you are keeping up. When it keeps growing, Backpressure slows the producers instead of letting the queue grow without limit.

Key takeaways

  • A message queue is a durable list of jobs: producers add work and return at once, and workers process it at a controlled pace.
  • For WhatsApp it absorbs bursts, paces sends to the number's throughput (80 per second by default) and survives worker restarts.
  • Acknowledge a job only after the Cloud API has returned a wamid and you have stored it.
  • Queues deliver at least once, so every send needs an idempotency key to make duplicates harmless.
  • Drain time is simply jobs divided by send rate: 120,000 messages at 80 per second take 25 minutes.

Design intent

Why does a message queue matter?

Without a queue, a send is only as reliable as the request that triggered it: a deploy, a Graph API timeout or a burst above 80 messages per second turns into lost or failed messages that nobody retries. With one, a Broadcast becomes a predictable stream that finishes at a known rate, a Meta incident becomes a backlog that drains afterwards, and the business can answer "when will everyone have it?" from queue depth and send rate alone.

Implementation

A message queue in code

import { Queue, Worker, UnrecoverableError } from "bullmq";
const connection = { host: "redis", port: 6379 };
const MESSAGES_URL =
"https://graph.facebook.com/v26.0/" + process.env.PHONE_NUMBER_ID + "/messages";
// Throttling and unknown errors are worth retrying; most other 4xx errors are not.
const RETRYABLE = new Set([4, 80007, 130429, 131000, 131056]);
// Producer: queue the send and return immediately.
export const sends = new Queue("whatsapp-sends", { connection });
await sends.add(
"template",
{ to: "919812345678", template: "exam_result", params: ["Riya", "R-4821"] },
{
jobId: "result-2026-r4821", // ignored if re-added while this job is still stored
attempts: 5,
backoff: { type: "exponential", delay: 2_000 },
removeOnComplete: 10_000,
removeOnFail: false, // keep failures for inspection and redrive
},
);
// Consumer: paced to one number's default throughput.
new Worker(
"whatsapp-sends",
async (job) => {
const res = await fetch(MESSAGES_URL, {
method: "POST",
headers: {
Authorization: "Bearer " + process.env.ACCESS_TOKEN,
"Content-Type": "application/json",
},
body: JSON.stringify({
messaging_product: "whatsapp",
to: job.data.to,
type: "template",
template: {
name: job.data.template,
language: { code: "en" },
components: [
{ type: "body", parameters: job.data.params.map((text) => ({ type: "text", text })) },
],
},
}),
signal: AbortSignal.timeout(15_000),
});
const data = await res.json();
const code = data.error?.code;
if (!res.ok && res.status < 500 && !RETRYABLE.has(code)) {
throw new UnrecoverableError("Permanent error " + code); // e.g. invalid recipient: no retries
}
if (!res.ok) throw new Error("Retryable error " + code); // backoff, then retry
return { wamid: data.messages[0].id }; // the job is acknowledged when this resolves
},
{ connection, concurrency: 20, limiter: { max: 80, duration: 1_000 } },
);

jobId makes repeat adds harmless, the limiter caps sends at 80 per second, and permanent errors skip straight to the failed set instead of burning retries.

Operating sequence

How does a message queue work?

  1. The producer queues the job

    Write the recipient, template, parameters, an idempotency key and a priority. Store references rather than large payloads, so each job stays small and self-contained.

  2. A worker leases the job

    A worker takes the next job, which stays invisible to other workers for the visibility timeout. Concurrency settings and a rate limiter keep the pool within the number's messages-per-second limit.

  3. The worker sends, then acknowledges

    Call the Messages Endpoint, store the wamid it returns, then acknowledge. If the process dies before acknowledging, the job reappears and is retried.

  4. Failures retry with backoff

    Transient errors (5xx, timeouts, 130429) are retried with exponential backoff; permanent ones (an invalid number, a paused template) are not. After the maximum number of attempts, the job moves to the dead-letter queue.

  5. Monitor depth and age

    Track how many jobs are waiting and how old the oldest one is. Rising age with a steady arrival rate means workers or throughput, not traffic, are the limit.

Specifications

A message queue: key facts and limits

Pacing target
80 messages per second per number (default)
Delivery guarantee
At least once: design for duplicates
When to acknowledge
After the wamid is stored, never before the send
Visibility timeout
Longer than the slowest send plus its retries
Health metrics
Queue depth and age of the oldest job
Drain time
Jobs ÷ send rate (120,000 ÷ 80 = 25 min)

Example: Exam results for 120,000 students

A Vadodara university queues semester-result notices for 120,000 students at 6:00 pm. Its business portfolio already has an unlimited messaging limit, so throughput is the only ceiling: its single number sends at the default 80 messages per second, so the queue drains in about 25 minutes (120,000 ÷ 80 = 1,500 seconds), an estimate the operations team shares with students in advance. Midway through, a worker node restarts: its 40 leased jobs reappear after the 60-second visibility timeout and other workers send them, with idempotency keys preventing duplicates. Fourteen jobs fail with an invalid-recipient error and land in the dead-letter queue for the data team to clean up.

Failure modes

Common mistakes with a message queue

  • Acknowledging before sending

    Acknowledging on receipt means a crash between the ack and the send loses the message silently. Acknowledge only after the Cloud API returned a wamid and you stored it.

  • A visibility timeout shorter than a send

    If a slow Graph API call outlives the lease, the job is redelivered while the first attempt is still running, and the customer may get two messages. Set the timeout above your request timeout plus retries.

  • One queue for everything

    OTPs stuck behind a 200,000-message broadcast arrive after they expire. Use separate queues or priorities for authentication, transactional and marketing traffic.

  • Retrying permanent errors

    An invalid recipient or a paused template fails every time, so retrying it five times wastes throughput. Classify errors and send permanent failures straight to the dead-letter queue.

  • Ignoring per-recipient order

    Parallel workers can deliver "Order shipped" before "Order confirmed". When Outbound Message Ordering matters for one customer, partition or group jobs by recipient.

Recommended practice

Best practices for a message queue

  • Alert on the age of the oldest job, not only on depth: age is what customers feel.
  • Let the producer generate the idempotency key once and store it in the job.
  • Set rate limits per phone number, because throughput is a per-number ceiling.
  • Remember that a queue paces speed, not reach: the portfolio's messaging limit still caps how many unique users you can message outside a service window in 24 hours.
  • Publish the expected drain time for big broadcasts so support and marketing know when sending will finish.

Message Queue FAQs

Why do I need a message queue to send WhatsApp messages?+
Because the Cloud API caps how fast each number can send and your traffic arrives in bursts. A queue holds the burst, sends at the allowed rate and retries what fails, so nothing is lost when a worker or the API has a bad minute.
What is the difference between a message queue and a dead-letter queue?+
The message queue is the main line every send goes through; the dead-letter queue holds jobs that failed too often or could not be processed. One keeps work flowing, and the other keeps failures out of its way for inspection and redrive.
How long will a WhatsApp broadcast take to send?+
Roughly the number of messages divided by your send rate. At the default 80 messages per second, 100,000 messages take about 21 minutes from one number; more numbers or upgraded throughput shorten it.
Does a message queue send each message exactly once?+
No. Most queues deliver at least once, so a job can run twice after a crash or a timeout. Make sends idempotent with an idempotency key stored alongside the wamid.

See InfiQ pricing

Put Message Queue to work

InfiQ is an official Meta Business Partner. Start a 7-day free trial — no card needed — and see this running on your own number.