Skip to main content

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.
Text
┌─────────────┐
│ Incoming │
│ Call │
└──────┬──────┘
│
▼
┌─────────────┐
│ Queue │
│ "Support" │
│ (on hold) │
└──────┬──────┘
│ strategy: ringall, linear, ...
▼
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Alice │ │ Bob │ │ Carol │
│ (agent) │ │ (agent) │ │ (agent) │
└─────────┘ └─────────┘ └─────────┘

Queues vs ring groups​

Ring groupCall queue
MembersUsers or phone numbersUsers only
BehaviorAll members ring in parallel; first to answer winsCaller waits on hold; agents dispatched per strategy
Caller experience while waitingRingback toneMusic on hold
If no member is availableCall ends after timeout_secondsCaller keeps waiting (subject to max_queue_length)
StrategiesOne (parallel)Seven (ringall, linear, rrmemory, leastrecent, fewestcalls, random, wrandom)
Per-agent stateNoneLogged-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​

JavaScript
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

Dispatch strategies​

strategy controls how the queue picks the next agent to ring.

StrategyBehaviorWhen to use
ringallRing every available agent in parallel; first to answer wins.Small teams where coverage matters more than load balancing.
linearRing agents in position order (lowest first).Tiered escalation — try Tier 1, then Tier 2, then Tier 3.
rrmemoryRound-robin, resuming after the last agent dialed.Even distribution across a steady-state team.
leastrecentRing the agent whose last call ended the longest ago.Fairness when call volume is uneven.
fewestcallsRing the agent with the fewest calls taken in the current window.Load balancing across a workday.
randomPick uniformly at random from available agents.Simple even spread without per-agent state.
wrandomWeighted 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.

An agent who is already on another call is skipped by default. Set ring_busy_agents to true to ring them anyway when that call did not come from a queue. An agent on a queue call is always skipped.

Managing members​

Queue members are users — each member references a user. A user can belong to multiple queues. Penalty and position are strategy hints (see Dispatch strategies).

Add a member​

JavaScript
await dialstack.queues.addMember(
'qu_01h2xcejqtf2nbrexx3vqjhp61',
{ user_id: 'user_01h2xcejqtf2nbrexx3vqjhp42' },
{ dialstackAccount: 'acct_01h2xcejqtf2nbrexx3vqjhp41' }
);

Adding the same user twice returns 409 Conflict.

Add a member with explicit penalty and position​

JavaScript
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:

Bash
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​

Bash
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). Optional reason string 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​

JavaScript
// 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',
});

status is derived from the underlying timestamps:

  • paused_at set → paused
  • logged_in_at unset → 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 by dialing a star code from any of their phones:

CodeAction
*45Toggle login / logout.
*46Toggle pause / unpause.

Dial the code on its own — there is no queue number to append. Agent state is stored per user, not per queue, so one *45 logs the agent out of every queue they belong to, and the next *45 logs them back in to all of them. There is no way to log out of one queue while staying logged in to another.

Each code is a toggle with no separate "log in" and "log out" code, so an agent who is unsure of their current state can dial *45 and listen to the confirmation, which announces the state they are now in.

Each star code plays a short audio confirmation and ends the call. The state change is applied to the user the dialing phone belongs to, so an agent's desk phone and softphone share one state — logging out on either logs the user out everywhere.

Logging out also clears any pause, so an agent who was paused before logging out comes back available rather than still paused.

Routing into a queue​

There are three ways to send callers into a queue.

1. Assign an extension number​

Create an extension whose target is the queue. Internal users dial the extension number to reach the queue.

JavaScript
await dialstack.extensions.create(
{
number: '500',
target: 'qu_01h2xcejqtf2nbrexx3vqjhp61',
},
{ dialstackAccount: 'acct_01h2xcejqtf2nbrexx3vqjhp41' }
);

2. Route to the queue from a dial plan​

Point an internal_dial node's target at the queue. Callers land in the queue when the plan reaches that node, after any earlier steps (schedules, menus, etc.). The node's next is the queue's timeout exit; leave it unset to run the queue's own timeout action. A phone number can also route straight to the queue as its routing target.

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.

JavaScript
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 waits for an agent to answer before the queue's timeout action fires. Range 0 to 3600 seconds (one hour), defaulting to 300. 0 means the 3600 maximum.

A ring already in progress when the wait is up is allowed to finish, so a caller an agent is being connected to is never cut off; the timeout action runs when that ring ends instead.

When the timeout elapses, the configured action runs:

  • ring_user — ring a fallback user. timeout.user is the user ID.
  • voicemail — send the caller to a user's voicemail or a shared voicemail box. timeout.voicemail accepts the ID of either a user or a shared voicemail box.
  • queue — overflow the caller into another queue, for tiered escalation. timeout.queue is the queue ID. A target that would close a routing loop, directly or transitively, is rejected when you save.

To change the timeout configuration, update the queue:

JavaScript
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:

JavaScript
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.