Every design so far has assumed a request gets an answer more or less immediately. A message queue breaks that assumption on purpose: a producer drops a message onto the queue and moves on immediately, and a consumer picks it up and processes it whenever it's able to — the two are decoupled in both time and availability.
A queue earns its place when at least one of these is genuinely true: the producer and consumer need to scale independently (a spike in uploads shouldn't require the resizing workers to scale in lockstep — they can just work through a growing backlog instead), the consumer is allowed to be temporarily unavailable without the producer failing (the whole point of decoupling covered in the async-communication trade-offs elsewhere in this reference), or the work genuinely needs to fan out to multiple independent consumers reacting to the same event.
It's needless complexity the moment none of those are true — specifically, when the caller actually needs an immediate answer to keep working (a queue can't give you that; the whole design is "fire and move on"), or when there's only ever one consumer that's always available anyway, in which case a direct call is simpler, faster, and has one fewer moving part that can fail. Reaching for a queue by default, on every service-to-service interaction, on the theory that "async is more scalable," adds a real piece of infrastructure — one more thing to run, monitor, and debug — that isn't buying you anything if nothing about the interaction actually needed decoupling.
Common mistake: Inserting a queue between two services purely because "microservices use queues," then discovering the caller actually needed a synchronous answer back and has to awkwardly poll for a result instead — reintroducing the exact latency the queue was supposed to hide, with extra steps.
Most real message queues guarantee at-least-once delivery: a message will be delivered, but might be delivered more than once — a consumer that crashes after processing a message but before acknowledging it will see that same message redelivered when it (or another consumer) comes back up. That means consumers have to be idempotent — processing the same message twice has to produce the same end result as processing it once, or duplicate deliveries turn into duplicate side effects (double-charging a customer, double-sending a notification).
Exactly-once delivery — guaranteeing a message is processed once and only once, with no duplicates and no drops — sounds like the obviously better guarantee, but it's genuinely difficult to provide across an unreliable network, and most systems that advertise it are really providing at-least-once delivery plus deduplication on the consumer side (tracking which message IDs have already been processed and skipping repeats), which amounts to the same idempotency requirement dressed up differently. In practice, designing consumers to be idempotent from the start — using a unique message ID and a "have I already handled this ID" check — is a more reliable path than trusting any queue's "exactly-once" label to mean there's genuinely nothing left for the application to handle.