Many messaging protocols ack in two steps: a fast "accepted, here's your message id" ack, followed later by a second, asynchronous ack that says whether it was actually delivered โ or rejected. A send() that resolves on the first ack is right for the common case and silently wrong for every case where the second ack is the one that mattered.
function naiveSend(transport, id) {
return new Promise((resolve) => {
transport.bus.once('ack', (ack) => {
resolve({ sent: true, stage: ack.stage }); // "sent: true" no matter what stage this was
});
transport.send(id);
});
}
The bug isn't in the promise, the event bus, or the transport โ it's that sent: true is returned on the first ack the code happens to see, not the ack that actually settles the question. The queued ack and the terminal (delivered/rejected) ack look identical to code that isn't checking which one it got.
node check.js --demo
Zero dependencies, no network โ a real event stream (Node's built-in EventEmitter) fires a fast "queued" ack, then an asynchronous "rejected" ack for the same message id, and two implementations race to answer "did it send."
This is the same shape flagged in a real WhatsApp automation thread we've engaged with directly: an account-class enforcement issue where the client-visible signals all looked fine (socket connects, message accepted) right up until an out-of-band signal โ invisible to the naive send path โ was the one that actually mattered. The specific trigger differs by protocol and platform; the shape โ a fast provisional ack mistaken for the real answer โ repeats across messaging integrations generally.