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.
┌─────────────┐
│ 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

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.

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

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

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

// 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 from any registered endpoint:

CodeAction
*45Toggle login / logout.
*46Toggle 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.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.

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.