Call Queues
Hold incoming callers and dispatch them to agents according to a configurable strategy.
Overview
A call queue answers the caller, places them on hold music, and rings agents (queue members) one by one — or all at once — until somebody picks up. Unlike a ring group, the queue persists callers between dispatch attempts: when no agent is available, the caller keeps waiting instead of the call ending.
Use cases:
- Support desk — distribute incoming tickets across a team without losing callers to busy signals.
- Sales overflow — let a primary salesperson take the first crack via a ring group, then overflow into a queue if nobody answers.
- After-hours fallback — small queue with a short timeout into voicemail.
┌─────────────┐
│ Incoming │
│ Call │
└──────┬──────┘
│
▼
┌─────────────┐
│ Queue │
│ "Support" │
│ (on hold) │
└──────┬──────┘
│ strategy: ringall, linear, ...
▼
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Alice │ │ Bob │ │ Carol │
│ (agent) │ │ (agent) │ │ (agent) │
└─────────┘ └─────────┘ └─────────┘
Queues vs ring groups
| Ring group | Call queue | |
|---|---|---|
| Members | Users or phone numbers | Users only |
| Behavior | All members ring in parallel; first to answer wins | Caller waits on hold; agents dispatched per strategy |
| Caller experience while waiting | Ringback tone | Music on hold |
| If no member is available | Call ends after timeout_seconds | Caller keeps waiting (subject to max_queue_length) |
| Strategies | One (parallel) | Seven (ringall, linear, rrmemory, leastrecent, fewestcalls, random, wrandom) |
| Per-agent state | None | Logged-in / paused / wrap-up tracked per user |
Ring groups are the right choice when every call needs to ring everyone, every time. Queues are the right choice when callers need to wait for an agent, agents need to manage their own availability, or you want fairness across many incoming calls.
Creating a queue
- SDK
- cURL
const queue = await dialstack.queues.create(
{
name: 'Support',
strategy: 'ringall',
timeout_seconds: 120,
wrap_up_seconds: 15,
max_queue_length: 50,
timeout: {
type: 'voicemail',
voicemail: 'svm_01h2xcejqtf2nbrexx3vqjhp60',
},
},
{ dialstackAccount: 'acct_01h2xcejqtf2nbrexx3vqjhp41' }
);
console.log(queue.id); // qu_01h2xcejqtf2nbrexx3vqjhp61
curl -X POST https://api.dialstack.ai/v1/queues \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "DialStack-Account: acct_01h2xcejqtf2nbrexx3vqjhp41" \
-H "Content-Type: application/json" \
-d '{
"name": "Support",
"strategy": "ringall",
"timeout_seconds": 120,
"wrap_up_seconds": 15,
"max_queue_length": 50,
"timeout": {
"type": "voicemail",
"voicemail": "svm_01h2xcejqtf2nbrexx3vqjhp60"
}
}'
Dispatch strategies
strategy controls how the queue picks the next agent to ring.
| Strategy | Behavior | When to use |
|---|---|---|
ringall | Ring every available agent in parallel; first to answer wins. | Small teams where coverage matters more than load balancing. |
linear | Ring agents in position order (lowest first). | Tiered escalation — try Tier 1, then Tier 2, then Tier 3. |
rrmemory | Round-robin, resuming after the last agent dialed. | Even distribution across a steady-state team. |
leastrecent | Ring the agent whose last call ended the longest ago. | Fairness when call volume is uneven. |
fewestcalls | Ring the agent with the fewest calls taken in the current window. | Load balancing across a workday. |
random | Pick uniformly at random from available agents. | Simple even spread without per-agent state. |
wrandom | Weighted random; lower penalty is more likely to be picked. | Mix of senior and junior agents where seniors should take more. |
wrap_up_seconds (0–600) gives an agent a cool-down after each call ends,
during which they aren't eligible for dispatch. leastrecent and
fewestcalls use the same per-user state to break ties.
Managing members
Queue members are users — each member references a user_id. A user can
belong to multiple queues. Penalty and position are strategy hints (see
Dispatch strategies).
Add a member
- SDK
- cURL
await dialstack.queues.addMember(
'qu_01h2xcejqtf2nbrexx3vqjhp61',
{ user_id: 'user_01h2xcejqtf2nbrexx3vqjhp42' },
{ dialstackAccount: 'acct_01h2xcejqtf2nbrexx3vqjhp41' }
);
curl -X POST https://api.dialstack.ai/v1/queues/qu_01h2xcejqtf2nbrexx3vqjhp61/members \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "DialStack-Account: acct_01h2xcejqtf2nbrexx3vqjhp41" \
-H "Content-Type: application/json" \
-d '{"user_id": "user_01h2xcejqtf2nbrexx3vqjhp42"}'
Adding the same user twice returns 409 Conflict.
Add a member with explicit penalty and position
await dialstack.queues.addMember(
'qu_01h2xcejqtf2nbrexx3vqjhp61',
{
user_id: 'user_01h2xcejqtf2nbrexx3vqjhp43',
penalty: 2,
position: 3,
},
{ dialstackAccount: 'acct_01h2xcejqtf2nbrexx3vqjhp41' }
);
List members
The first 10 members are embedded inline on the queue resource under
members. Use the next_page_url (or call the standalone endpoint
directly) to page through the rest:
curl https://api.dialstack.ai/v1/queues/qu_01h2xcejqtf2nbrexx3vqjhp61/members \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "DialStack-Account: acct_01h2xcejqtf2nbrexx3vqjhp41"
Remove a member
curl -X DELETE \
https://api.dialstack.ai/v1/queues/qu_01h2xcejqtf2nbrexx3vqjhp61/members/qum_01h2xcejqtf2nbrexx3vqjhp62 \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "DialStack-Account: acct_01h2xcejqtf2nbrexx3vqjhp41"
Agent state
Each user that's a member of any queue has an availability state:
available— eligible for dispatch.paused— logged in but temporarily skipped (break, training, focus time). Optionalreasonstring for the pause.logged_out— not eligible for dispatch on any queue.
State is maintained per user (not per queue), so an agent in three queues toggles availability for all three at once.
Read or update agent state
- SDK
- cURL
// Mark available
await dialstack.users.updateQueueAgent(
'user_01h2xcejqtf2nbrexx3vqjhp42',
{ status: 'available' },
{ dialstackAccount: 'acct_01h2xcejqtf2nbrexx3vqjhp41' }
);
// Pause with a reason
await dialstack.users.updateQueueAgent(
'user_01h2xcejqtf2nbrexx3vqjhp42',
{ status: 'paused', reason: 'break' },
{ dialstackAccount: 'acct_01h2xcejqtf2nbrexx3vqjhp41' }
);
// Log out
await dialstack.users.updateQueueAgent(
'user_01h2xcejqtf2nbrexx3vqjhp42',
{ status: 'logged_out' },
{ dialstackAccount: 'acct_01h2xcejqtf2nbrexx3vqjhp41' }
);
// Read current state
const agent = await dialstack.users.retrieveQueueAgent('user_01h2xcejqtf2nbrexx3vqjhp42', {
dialstackAccount: 'acct_01h2xcejqtf2nbrexx3vqjhp41',
});
curl -X POST https://api.dialstack.ai/v1/users/user_01h2xcejqtf2nbrexx3vqjhp42/queue-agent \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "DialStack-Account: acct_01h2xcejqtf2nbrexx3vqjhp41" \
-H "Content-Type: application/json" \
-d '{"status": "paused", "reason": "break"}'
status is derived from the underlying timestamps:
paused_atset →pausedlogged_in_atunset →logged_out- otherwise →
available
Toggling between available and paused preserves logged_in_at.
Logging out clears paused_at, pause_reason, and logged_in_at, but
the row itself is retained so historical attributes survive a
logout/login cycle.
Star codes
Agents can manage their own state from any registered endpoint:
| Code | Action |
|---|---|
*45 | Toggle login / logout. |
*46 | Toggle pause / unpause. |
Each star code plays a short audio confirmation and ends the call. The state change is applied to the user account associated with the dialing endpoint.
Routing into a queue
There are three ways to send callers into a queue.
1. Assign an extension number
Create an extension whose extension_type is queue and whose
queue_id points at the queue. Internal users dial the extension number
to reach the queue.
await dialstack.extensions.create(
{
extension_number: '500',
extension_type: 'queue',
queue_id: 'qu_01h2xcejqtf2nbrexx3vqjhp61',
},
{ dialstackAccount: 'acct_01h2xcejqtf2nbrexx3vqjhp41' }
);
2. Point a phone number's dial plan at the queue
In the dial plan editor, set the destination of the relevant rule (or the default rule) to the queue. Inbound calls to that number land in the queue once any earlier rules (schedules, IVR, etc.) finish.
3. Overflow from a ring group
Set a ring group's timeout_action to queue and timeout_target to
the queue ID. When the ring group times out, the caller is moved into
the queue rather than the call ending.
await dialstack.ringGroups.update(
'rg_01h2xcejqtf2nbrexx3vqjhp51',
{
timeout_action: 'queue',
timeout_target: 'qu_01h2xcejqtf2nbrexx3vqjhp61',
},
{ dialstackAccount: 'acct_01h2xcejqtf2nbrexx3vqjhp41' }
);
Timeout behavior
Two limits govern how long a caller stays in the queue.
timeout_seconds
How long any individual caller can wait before the queue's timeout
action fires. 0 means no timeout — the caller waits indefinitely.
When the timeout elapses, the configured action runs:
ring_user— ring a fallback user.timeout.useris the user ID.voicemail— send the caller to a user's voicemail or a shared voicemail box.timeout.voicemailaccepts the ID of either a user or a shared voicemail box.
To change the timeout configuration, update the queue:
await dialstack.queues.update(
'qu_01h2xcejqtf2nbrexx3vqjhp61',
{
timeout_seconds: 180,
timeout: { type: 'voicemail', voicemail: 'svm_01h2xcejqtf2nbrexx3vqjhp60' },
},
{ dialstackAccount: 'acct_01h2xcejqtf2nbrexx3vqjhp41' }
);
To clear the timeout configuration entirely, send timeout: null:
await dialstack.queues.update(
'qu_01h2xcejqtf2nbrexx3vqjhp61',
{ timeout: null },
{ dialstackAccount: 'acct_01h2xcejqtf2nbrexx3vqjhp41' }
);
max_queue_length
How many callers can wait at the same time. 0 means unlimited.
When the limit is reached:
- External callers hear a short "queue full" announcement and the call ends.
- Internal callers are treated as busy, so the calling phone can fall back through its dial plan as if the queue were unreachable.
max_queue_length is a hard rejection at queue entry — it is not the
same as timeout_seconds. The timeout action only applies to callers
who waited past timeout_seconds, not to callers who were rejected
because the queue was full.