PearlPBX2
← All posts

Keep callers on the line: useful hold in Asterisk

A caller dials your number, hears ringing and waits. One ring, two, three. Around the fourth, many of them hang up, because from their side it looks exactly like nobody is answering. Meanwhile your operators may be free and about to pick up, but the call is already lost.

The caller isn’t at fault here. Ringback tells them nothing. So instead of ringing, give them something useful and let them see what is going on. Below is how to do that in Asterisk, with AEL dialplan examples for PearlPBX2.

The idea

The caller hears something useful from the first second, and the operators are being called at the same time, not after the message finishes. To get this, you don’t put a separate Playback before the queue. You put the greeting and the useful phrases first in the music-on-hold class. The dialplan answers the call and passes it straight to Queue:

&call_limit();
NoOp(CALL BEGIN >>>> :'${CALLERID(name)}'@<${CALLERID(num)}>);
Set(CHANNEL(language)=en);
Set(TIMEOUT(absolute)=3600);
if (${LEN(${CALLERID(name)})} > 7) {
    Set(CALLERID(num)=${CALLERID(name)});
}
&callerid_normalization();
AGI(agi://127.0.0.1:4573/blacklist,${CALLERID(num)});
if ( ${BLACKLISTED} > 0) {
    Hangup(17);
}
AGI(agi://127.0.0.1:4573/whitelist,${CALLERID(num)});
if ( ${WHITELISTED} > 0) {
    Set(QUEUE_PRIO=10);
}
AGI(agi://127.0.0.1:4573/mixmonitor,${CALLERID(num)},${EXTEN});
&lc_partday();
Set(CHANNEL(musicclass)=my_moh_class_${PARTDAY});
Answer();
Wait(1);
Queue(support,tT,,,90,crm_integration_agi.py);
NoOp("QUEUESTATUS=${QUEUESTATUS}");
if ("${QUEUESTATUS}" = "TIMEOUT") {
    Playback(all_operators_busy);
}
Hangup();

Look at the end of the script. There is no Playback between Answer() and Queue(), only a one-second pause. The greeting plays as part of the music on hold, so the caller listens to it while Asterisk is already ringing the operators. If an operator is free, the conversation starts without a wasted second. If nobody is free, the caller hears something useful instead of empty ringing.

Everything before the queue is a gate:

  • &call_limit() and &callerid_normalization() are macros from the dialplan editor: a call limit and caller number normalization.
  • blacklist rejects unwanted callers before the call is answered, and whitelist raises queue priority (QUEUE_PRIO=10).
  • mixmonitor starts recording according to the rules set in the admin.
  • &lc_partday() sets PARTDAY (morning, day, evening, night), which selects the music class.

After Queue, the script logs QUEUESTATUS. If nobody answered within 90 seconds (TIMEOUT), the caller hears all_operators_busy before the call ends.

Building the music class

The class my_moh_class_morning (and likewise _day, _evening, _night) is created in the admin in Files mode with Alpha sorting. Name the files so the useful phrase comes first alphabetically:

01_greeting.wav
02_music_1.wav
03_music_2.wav

Every caller then starts with 01_ instead of a random point in the playlist. A class set through CHANNEL(musicclass) overrides the queue’s own class: the musiconhold.conf that PearlPBX2 generates contains preferchannelclass=yes. So one queue can serve any number of classes, and the dialplan picks one.

The first file is the place to say that the caller reached the right place, that the operators are on calls, and that an answer is coming soon. The scenarios below build on this basic one.

Why ringback loses

Ringback tells the caller one thing: the line is free and nobody has picked up yet. They can’t tell how long to wait, whether anyone is there, or whether they dialed the wrong number. So they decide whether to keep waiting blindly, and the longer the silence, the more often the answer is no.

Once you answer the call (Answer), you can talk to the caller. No operator appears on the line because of it, but the caller hears you instead of silence.

Step 1. The simpler variant: a phrase before the queue

If you would rather not touch the music class, there is a minimal version: answer, pause briefly, say a phrase, queue the call. The downside is that the phrase plays before ringing starts, so every second of it is a second when no operator is being called. Keep it short.

Set(CHANNEL(language)=en);
Answer();
Wait(1);
Playback(welcome-hold);
Queue(Support,tT,,,90);
Playback(sorry-call-later);
Hangup();

Wait(1) is there so the start of the phrase isn’t lost while the connection settles. welcome-hold is your own recording from the sound library in the admin, without a file extension. The phrase should tell the caller whether they reached the right place, what happens next, and whether it’s worth waiting. For example: “You have reached support. All operators are on calls right now, and we will answer within a minute. Please stay on the line.”

In Queue(Support,tT,,,90) the fifth argument is the timeout in seconds. If nobody answers within 90 seconds, Queue returns control to the dialplan, and the last Playback asks the caller to try again later instead of dropping them into silence.

Step 2. Give them information while they wait

PearlPBX2 queue settings already cover this, and you don’t need dialplan code for it:

  • Queue position (announce_position) and estimated wait (announce_holdtime): the caller hears that they are third and should expect about two minutes. The “is anyone there?” question goes away.
  • A periodic announcement (periodic_announce with periodic_announce_frequency): a comma-separated list of files played at a set interval. Good material is whatever callers ask operators anyway: opening hours, the website address, where to check an order status.
  • Music on hold by time of day: the class is chosen through CHANNEL(musicclass), and you can put information in it too. How to build the greeting into the music so it plays while operators are already being called is covered in a separate article about incoming calls into a queue.

All of this is set in the queue form in the admin. What to turn on depends on your call flow. Two or three short phrases work better than one long one.

Step 3. Don’t queue blindly

If every operator is busy and several people are already waiting, it is more honest to say so up front and offer a way out. The PearlPBX2 FastAGI server has a queue-status handler for this. It sets READYTORECEIVE (how many operators are available) and QUEUECALLERS (how many callers are already waiting).

Answer();
Wait(1);
AGI(agi://127.0.0.1:4573/queue-status,Support);
if (${READYTORECEIVE} > 0) {
    Playback(welcome-hold);
    Queue(Support,tT,,,90);
} else {
    Playback(all-busy-wait);
    Queue(Support,tT,,,120);
}

The handler reads its data from Redis, where the dashboard service writes it. If the dashboard isn’t running or Redis is unavailable, it queries AMI directly.

The branches differ in the phrase. When an operator is free, a short phrase is enough and the call connects within seconds. When none is free, the phrase should honestly warn about the wait.

Step 4. Offer a callback instead of a wait

The best outcome is not making the caller wait at all. The add-callback handler stores a callback request in the database, and the callback daemon calls the person back through AMI. The caller presses one digit and hangs up.

Answer();
Wait(1);
AGI(agi://127.0.0.1:4573/queue-status,Support);
if (${READYTORECEIVE} > 0) {
    Playback(welcome-hold);
    Queue(Support,tT,,,90);
} else {
    Read(CALLBACK_CHOICE,busy-offer-callback,1,,1,5);
    if ("${CALLBACK_CHOICE}" = "1") {
        AGI(agi://127.0.0.1:4573/add-callback,${CALLERID(num)},${EXTEN},Support,30);
        if (${CALLBACK_ADDED} = 1) {
            Playback(callback-scheduled);
            Hangup();
        }
    }
    Playback(welcome-hold);
    Queue(Support,tT,,,120);
}
Playback(sorry-call-later);
Hangup();

What happens here:

  • Read plays a prompt such as “All operators are busy, press 1 and we will call you back” and waits up to five seconds for one digit. The value goes into CALLBACK_CHOICE.
  • The variable is quoted in the comparison. If the caller presses nothing, the variable is empty, and without quotes the expression ${CALLBACK_CHOICE} = 1 becomes a syntax error in Asterisk.
  • The add-callback arguments are the caller’s number, the dialed number, the callback service name and the delay in seconds. A service with that name must exist in the callback settings.
  • CALLBACK_ADDED is 1 when the request was stored. Only then does the caller hear “we will call you back” and the call ends. Otherwise they go into the queue as before.

A caller who presses nothing loses nothing: they enter the queue with the same timeout.

Step 5. What to do after a timeout

When Queue returns control, the reason is in ${QUEUESTATUS} (for example TIMEOUT). The final Playback(sorry-call-later) in the script above is the minimum. You can repeat the callback offer here, in the same block, so a caller who waited two minutes doesn’t leave with nothing.

What this gives you

None of this adds operators, but it changes what happens to the caller during the same seconds. They hear a voice right away and know they reached the right place. They know roughly how long to wait. And if they don’t want to wait, they have a way out.

In PearlPBX2 this is assembled from parts that already exist: the dialplan editor with validation before apply, the queue-status and add-callback FastAGI handlers, the callback daemon, and the queue announcement settings. The dashboard shows how many callers are waiting in a queue right now, and the reports show missed calls.