The send queue
Messages you start wait in the queue, even for people you have contacted before. We accept and charge each message, then release them one at a time, 15 to 25 minutes apart by default. Replies to a message received in the last 24 hours go out now.
What actually happens
Send to someone who has not written to your number in the last 24 hours, and the API answers queued instead of sent:
{
"data": {
"id": "9c1f0b7e-8b1a-4a2e-9b0a-1f2e3d4c5b6a",
"to": "+5511988887777",
"status": "queued",
"scheduledAt": "2026-08-24T19:18:00.000Z",
"quotaRemaining": 4999,
"newConversationsRemaining": 19
}
}The message quota is spent when the call accepts it. scheduledAt is its estimated send time. Queued messages leave 15 to 25 minutes apart by default, with the gap picked at random, one at a time per number. The gap is measured from the previous message's actual release, not an old schedule, so a backlog cannot leave all at once. Until it leaves, id is the queue id, not WhatsApp's message id.
Why we hold it
Sending many messages quickly can look like spam, even when you have contacted those people before. Slowing the queue prevents an accidental burst. It does not guarantee that WhatsApp will keep the number connected.
The failure this exists for is dull and common. You send a list of fifteen, your own system adds four more behind them, and the number is gone by lunchtime. Nobody meant to spam anyone. The burst is an accident of software being fast.
Everything else in avoiding bans still applies. This fixes the timing, which is the part that is hardest to get right by hand.
What is never held
These are the exceptions for individual sends. While pacing is on, every message in a batch waits the full configured gap, even replies and messages to known contacts.
- Recent replies go out now. The recipient must have sent a message to this number in the last 24 hours. A contact saved on the phone, a past outgoing message or a chat imported during pairing is not enough.
send_otpnever waits and never shows "typing…", because someone is waiting at a login form. A first code to someone new still spends the day's new-conversation budget.skipQueue: trueskips the queue for that send. Use it only when the recipient is expecting the message now. Group messages also skip the queue on individual sends.- A number with
pacing.enabled: falsesends everything immediately, and spacing the messages out becomes your job.
await client.sendMessage({
to: "+5511988887777",
text: `Your code is ${code}`,
// Only for something the recipient is waiting for RIGHT NOW.
skipQueue: true,
});Knowing when it left
The send call cannot give you WhatsApp's message id, because nothing has been sent yet. message.sent arrives on your webhook when the message finally goes out. It carries the queue id you already have, next to WhatsApp's own messageId, which is the one the delivery receipts will use.
{
"id": "6f1e…",
"type": "message.sent",
"timestamp": "2026-08-24T19:18:04.000Z",
"data": {
"number": { "id": "a1b2…", "name": "Outreach", "phone": "5511900000000" },
"id": "9c1f0b7e-8b1a-4a2e-9b0a-1f2e3d4c5b6a",
"messageId": "3EB0C7E5F5A2B4D9E1F3",
"to": "+5511988887777",
"queuedAt": "2026-08-24T19:00:00.000Z",
"sentAt": "2026-08-24T19:18:04.000Z"
}
}If the number disconnects or is restricted, messages that have not left stay queued. They resume with the configured spacing when the number reconnects. A definite refusal, such as a media url that no longer works, can produce message.failed and a refund. If delivery is unknown after a timeout or interrupted send, we do not replay it or refund it as an unsent message. Check the chat and delivery receipts before sending again.
Seeing and cancelling what waits
GET /v1/queue lists messages still waiting, soonest first. Cancel before a send begins. The answer says whether its message quota was returned, and any new-conversation budget it reserved is released. The dashboard shows the account's queue on Messages and each number's queue on Numbers.
const waiting = await client.listQueuedMessages({ numberId });
// [{ id, to, kind, preview, scheduledAt, queuedAt }, …]
await client.cancelQueuedMessage({ id: waiting[0].id });
// → { canceled: true, refunded: true }Changing the gap
The gap belongs to the number and defaults to 15–25 minutes. Change it with update_number, or in the dashboard under Numbers.
await client.updateNumber({
numberId,
pacing: { minMinutes: 30, maxMinutes: 90 }, // slower, safer
humanize: { markRead: false }, // no blue ticks from us
});You can switch the queue off, but then you must space the sends yourself. This also applies when you use skipQueue. Avoid bursts and only message people who expect to hear from you.
Sending like a person
The second half of the same idea: a send is performed the way a person would do it, not the way software would.
- The number comes online just before it sends, and goes offline after.
- It opens what the recipient last wrote, which shows blue ticks, before answering.
- It shows “typing…” for as long as the message would take to type.
This costs a few seconds per call and is on by default. Turn any part of it off per number with update_number, or for one message with humanize: false.
What this is not
It is not a scheduler. You choose a gap, never a delivery time. It is not a delivery guarantee either. WhatsApp still decides whether a message lands, and the receipts still arrive on message.status.
And it is no substitute for having something worth sending. A queue paces a bad list perfectly well. It does not stop the people on it from tapping Block.