Voicemail & Answering Machine Detection

Take a message when nobody answers (record@v1 + saveRecordingFile@v1), notify your team by email or SMS, and detect answering machines on outbound calls with amd@v3.

👍

Two sides of the same problem: inbound, nobody picks up and the caller wants to leave a message; outbound, your campaign reaches a voicemail and you want to know, leave a message, or hang up.

📥 Inbound: Take a Message

How It Works

  1. dial@v2 tries to reach your team. $dial.ok is false when nobody answered.
  2. record@v1 plays a beep and records the caller until silence, a key press, or maxLength. Its result is a temporary file.
  3. saveRecordingFile@v1 turns that file into a Recording of your account (an MP3 URL, listed in the Recordings API), with an optional transcript.
  4. events.ready of saveRecordingFile@v1 runs when the recording (and transcript) is ready: email it, text it, push it to your CRM.

Example: Forward, Then Voicemail

description: Forward the call, take a message if nobody answers, email it

compat:
  version: "2025-09-01"

defaults:
  language: en-US

variables:
  $team: "+33199001020"
  $notify: [email protected]
  $dial: null
  $record: null
  $recording: null

branches:
  inbound-call:
    actions:
      - action: dial@v2
        params:
          targets:
            - number: ${{ $team }}
              timeout: 25
        result: $dial

      # Nobody answered: take a message
      - if: '!$dial.ok'
        then:
          - action: play@v3
            params:
              tts:
                text: We are not available right now. Please leave a message after the beep, and press any key when you are done.

          - action: record@v1
            params:
              maxLength: 120
              timeout: 5 # stop after 5 seconds of silence
            result: $record

          - action: saveRecordingFile@v1
            params:
              file: ${{ $record }}
              transcription:
                enabled: true
                language: en-US
            events:
              ready:
                - action: email@v1
                  params:
                    to: ${{ $notify }}
                    subject: New voicemail from ${{ call.fromNumber || "an anonymous caller" }}
                    text: |
                      Caller: ${{ call.fromNumber || "anonymous" }}
                      Duration: ${{ recording.duration }} seconds
                      Listen: ${{ recording.url }}

                      Transcript:
                      ${{ chat_text }}
            result: $recording

          - action: play@v3
            params:
              tts:
                text: Thank you, goodbye.

      - action: hangup@v1
📘

events.ready runs after the call, like the hangup branch. Use sms@v1 instead of email@v1 to text the team, or fetch@v3 to create a ticket. See Record and Transcribe Calls for the variables available there.

Let the Caller Review the Message

playRecordingFile@v1 plays the temporary file back before you save it.

- action: record@v1
  params:
    maxLength: 120
  result: $record

- action: play@v3
  params:
    tts:
      text: Here is your message.

- action: playRecordingFile@v1
  params:
    file: ${{ $record }}

- action: gather@v4
  params:
    tts:
      text: Press 1 to send it, or 2 to record it again.
    maxDigits: 1
    timeout: 10
  result: $choice

- if: $choice.input === "1"
  action: saveRecordingFile@v1
  params:
    file: ${{ $record }}
  result: $recording

record@v1 Parameters

ParameterDefaultDescription
maxLength30Maximum length of the message, in seconds (5 to 300).
timeout5Seconds of silence that end the recording (0 to 10).
beeptruePlay a beep before recording.
finishOnAnyKeytrueAny key press ends the recording.

📤 Outbound: Detect Answering Machines

How It Works

On a Broadcast Call, the called party answers, and your scenario continues in the answerOnBranch branch — whether a human or a voicemail picked up. amd@v3 listens for a few seconds and tells you which one it is:

  • a human says a short greeting ("Hello?") and waits;
  • a machine plays a long greeting ("Hi, you have reached…, please leave a message after the tone").

amd@v3 counts the words it hears: more than minWords means a machine. yesHints and noHints short-circuit the decision when a known word is heard.

Example: Leave a Message on Voicemails Only

description: Call a list of people; talk to humans, leave a message on voicemails

compat:
  version: "2025-09-01"

defaults:
  language: en-US

variables:
  $target: "+33639980010" # passed when starting the scenario
  $dial: null
  $amd: null
  $silence: null

branches:
  api-initiated:
    actions:
      - action: dial@v2
        params:
          callerId: "+33199001234"
          targets:
            - number: ${{ $target }}
              timeout: 30
          answerOnBranch: answered
        result: $dial

  answered:
    actions:
      - action: amd@v3
        params:
          timeout: 5
          minWords: 4
          yesHints: [message, voicemail, tone, beep]
          noHints: [hello, yes]
        result: $amd

      # A voicemail: wait for the end of the greeting, then leave the message
      - if: $amd.detected === "machine"
        then:
          - action: waitForSilence@v1
            params:
              silence: 2
              timeout: 20
            result: $silence
          - action: play@v3
            params:
              tts:
                text: Hello, this is Acme. Your order is ready for pickup. See you soon!
          - action: hangup@v1

      # A human (or no decision): talk to them
      - action: play@v3
        params:
          tts:
            text: Hello, this is Acme. Your order is ready for pickup.

      - action: gather@v4
        params:
          tts:
            text: Press 1 to confirm you will come today, or 2 for tomorrow.
          maxDigits: 1
          timeout: 10
        result: $answer

      - action: hangup@v1
🚧

Answering machine detection is a heuristic: it listens to the first seconds of the call. A human who answers with a long sentence can be mistaken for a machine, and a short voicemail greeting for a human. Keep timeout short (5 seconds is a good start) and tune yesHints/noHints with the words your callees actually use.

amd@v3 Result and Parameters

result:
  detected: machine   # machine | human | "" (no decision before the timeout)
  recognized: message # the hint that matched, if any
  timeout: false
ParameterDefaultDescription
timeout6Seconds to listen (1 to 10).
minWords4More words than this means a machine.
yesHints—Words that mean a machine, in the callee's language (up to 10).
noHints—Words that mean a human (up to 10).
language—Speech-to-text language. Defaults to the language of the called number.
tts / audioFile—Optional prompt played while listening (set at most one). Key presses are ignored.

💡 Going Further

  • Business hours: send callers straight to voicemail outside opening hours, with a javascript@v1 check on call.startedAt.
  • Keep a log of every attempt: push $dial.status and $amd.detected to your server from the hangup branch, so your campaign can retry the voicemails later. See Push Data to Your Platform.
  • Summarize voicemails with AI: in events.ready, send chat_text to openai@v3 before emailing it. See Record and Transcribe Calls.

Did this page help you?