Twilio logo

Skill

twilio-voice-outbound-calls

make outbound phone calls via Twilio

Published by Twilio Updated Jul 17
Covers Automation API Development Communications

Description

Make outbound phone calls via Twilio's Programmable Voice REST API. Covers the full voice platform: calls.create(), answering machine detection (AMD), conference-based agent bridging, call recording, status tracking, and SIP Trunking. Use this skill for outbound calls, sales dialers, or when asking what voice APIs are available.

SKILL.md

Overview

Agent safety: Before placing an outbound call, always confirm the recipient number and intent with the user. Outbound calls are irreversible and may incur charges. For automated systems, implement TCPA compliance: obtain prior express consent, respect quiet hours (8 AM–9 PM recipient local time), and maintain a Do Not Call list.

Every outbound call requires a from Twilio number, a to recipient, and TwiML instructions that define what happens when the call is answered — either as a webhook URL or inline.


Voice Platform Capabilities

Outbound calls go beyond basic calls.create(). Here's what you can build:

CapabilityHowWhen to use
Basic outbound callcalls.create() with TwiML or webhook URLAny outbound call — see Quickstart below
Answering Machine Detection (AMD)machineDetection parameter on calls.create()Sales dialers, call campaigns — filter voicemail from humans
Conference-based agent bridging<Dial><Conference> in TwiMLConnect agents to live prospects with whisper, barge, hold
SIP TrunkingElastic SIP TrunkingBring your own carrier for outbound calls — cost reduction at scale
Call Recordingrecord=True on calls.create() or <Record> verbCompliance, QA, training
Voice InsightsAutomatic per-call metricsCall quality monitoring — latency, jitter, packet loss, answer rates
AI Voice AgentsConversationRelay + LLMReal-time speech recognition → LLM → TTS for conversational AI

For TwiML verbs (Say, Gather, Dial, Record, Conference, Pay), see twilio-voice-twiml. For AI voice agents, see twilio-voice-conversation-relay.


Prerequisites

  • Twilio account with a voice-capable phone number — New to Twilio? See twilio-account-setup for signup, getting a number, and trial limitations — Trial accounts can only call verified numbers
  • Environment variables:
    • TWILIO_ACCOUNT_SID
    • TWILIO_AUTH_TOKEN — See twilio-iam-auth-setup for credential setup and best practices
  • SDK: pip install twilio / npm install twilio

Quickstart

Python

import os
from twilio.rest import Client

client = Client(os.environ["TWILIO_ACCOUNT_SID"], os.environ["TWILIO_AUTH_TOKEN"])

call = client.calls.create(
    from_="+15017122661",    # Your Twilio number (E.164)
    to="+15558675310",       # Recipient (E.164)
    twiml="<Response><Say>Your order has shipped. Goodbye.</Say></Response>"
)

print(call.sid)     # CAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
print(call.status)  # queued | ringing | in-progress | completed | failed

Node.js

const twilio = require("twilio");
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);

const call = await client.calls.create({
    from: "+15017122661",
    to: "+15558675310",
    twiml: "<Response><Say>Your order has shipped. Goodbye.</Say></Response>",
});

console.log(call.sid);
console.log(call.status);

Key Patterns

Use a Webhook URL for Dynamic Call Handling

Pass a url instead of inline twiml — Twilio POSTs to your server when the call connects and executes the TwiML you return.

Python

call = client.calls.create(
    from_="+15017122661",
    to="+15558675310",
    url="https://yourapp.com/twiml/welcome"
)

Node.js

const call = await client.calls.create({
    from: "+15017122661",
    to: "+15558675310",
    url: "https://yourapp.com/twiml/welcome",
});

Python (Flask) — TwiML webhook handler

from flask import Flask
from twilio.twiml.voice_response import VoiceResponse

app = Flask(__name__)

@app.route("/twiml/welcome", methods=["POST"])
def welcome():
    response = VoiceResponse()
    response.say("Hello! Press 1 to hear your account balance.")
    response.gather(num_digits=1, action="/twiml/handle-input")
    return str(response)

Node.js (Express) — TwiML webhook handler

const { VoiceResponse } = require("twilio").twiml;

app.post("/twiml/welcome", (req, res) => {
    const response = new VoiceResponse();
    response.say("Hello! Press 1 to hear your account balance.");
    response.gather({ numDigits: 1, action: "/twiml/handle-input" });
    res.type("text/xml").send(response.toString());
});

For all TwiML verbs (Say, Gather, Dial, Record, Conference), see twilio-voice-twiml.

Track Call Status

Python

call = client.calls.create(
    from_="+15017122661",
    to="+15558675310",
    url="https://yourapp.com/twiml/welcome",
    status_callback="https://yourapp.com/call-status",
    status_callback_method="POST"
)

Node.js

const call = await client.calls.create({
    from: "+15017122661",
    to: "+15558675310",
    url: "https://yourapp.com/twiml/welcome",
    statusCallback: "https://yourapp.com/call-status",
    statusCallbackMethod: "POST",
});

Status transitions: queued → ringing → in-progress → completed (or failed/busy/no-answer).

Record a Call

Python

call = client.calls.create(
    from_="+15017122661",
    to="+15558675310",
    url="https://yourapp.com/twiml/welcome",
    record=True,
    recording_status_callback="https://yourapp.com/recording-ready"
)

Node.js

const call = await client.calls.create({
    from: "+15017122661",
    to: "+15558675310",
    url: "https://yourapp.com/twiml/welcome",
    record: true,
    recordingStatusCallback: "https://yourapp.com/recording-ready",
});

Recordings accessible at https://api.twilio.com/2010-04-01/Accounts/{AccountSid}/Recordings.


Answering Machine Detection (AMD)

Detect whether a human or voicemail answers before connecting an agent or leaving a message. Two modes:

ModeBehaviorBest for
EnableReturns result immediately when human/machine first detectedSales dialers — connect agent to humans ASAP
DetectMessageEndWaits for voicemail greeting to finish (beep/silence)Leaving voicemail — wait for beep, then play/record message

Python — Sales dialer (connect agents to live answers only)

call = client.calls.create(
    from_="+15017122661",
    to="+15558675310",
    url="https://yourapp.com/handle-answer",
    machine_detection="Enable",  # Immediate detection — use for live-agent connect
    async_amd=True,              # Non-blocking — call connects while AMD analyzes
    async_amd_status_callback="https://yourapp.com/amd-result",
    async_amd_status_callback_method="POST"
)

Node.js

const call = await client.calls.create({
    from: "+15017122661",
    to: "+15558675310",
    url: "https://yourapp.com/handle-answer",
    machineDetection: "Enable",
    asyncAmd: true,
    asyncAmdStatusCallback: "https://yourapp.com/amd-result",
    asyncAmdStatusCallbackMethod: "POST",
});

AMD webhook delivers AnsweredBy parameter:

ValueMeaningAction
humanLive person detectedConnect to agent
machine_startMachine detected, greeting still playingHang up or wait
machine_end_beepVoicemail beep heardLeave message
machine_end_silenceSilence after greetingLeave message
faxFax tone detectedHang up
unknownCould not determineTreat as human or retry

Conference-based agent bridging (production pattern for sales dialers):

# In /handle-answer webhook — when AMD says "human":
response = VoiceResponse()
dial = response.dial()
dial.conference(
    f"Sales-{call_sid}",
    start_conference_on_enter=False,  # Prospect waits for agent
    end_conference_on_exit=True
)

# Separately, dial agent into same conference:
client.calls.create(
    from_="+15017122661",
    to=agent_number,
    twiml=f'<Response><Dial><Conference startConferenceOnEnter="true">Sales-{call_sid}</Conference></Dial></Response>'
)

This pattern gives you call control (whisper to agent before connecting, supervisor barge-in, hold) that direct <Dial> does not.

Cost: AMD adds ~$0.0075 per call to standard voice pricing.


Response Fields

FieldDescription
sidCall identifier (CA...)
statusqueued, ringing, in-progress, completed, failed, busy, no-answer
durationLength in seconds (after completion)
priceCost (populated after completion)

Common Errors

CodeMeaningFix
21211Invalid to numberValidate E.164 format
13224Invalid TwiML at webhook URLValidate TwiML response from your server
13225TwiML URL returned non-200 statusFix your webhook endpoint

CANNOT

  • Cannot use a private TwiML URL — Must be publicly accessible. Use ngrok for local development only; deploy to a cloud provider for production.
  • Cannot exceed ~4,096 characters in inline twiml parameter — Use a TwiML URL for longer responses
  • Cannot call unverified numbers on trial accounts — Upgrade to paid or verify recipient numbers first
  • AMD accuracy is ~85-90% — Expect some false positives/negatives. Tune machineDetectionSpeechThreshold (default 2400ms) based on your results.
  • AMD adds latencyEnable mode returns results faster but may misclassify short greetings. DetectMessageEnd is more accurate but adds seconds before your app can act.
  • Cannot use AMD with SIP Trunking — AMD is only available on calls made via the Calls API

Next Steps

  • TwiML verb reference (Say, Gather, Dial, Record, Conference, Pay): twilio-voice-twiml
  • AI voice agents with real-time speech/LLM: twilio-voice-conversation-relay
  • Improve answer rates (Branded Calling, STIR/SHAKEN): twilio-numbers-senders

© 2026 YourAI.tools. Every skill from an identity-verified publisher.

Independent catalog. Not affiliated with, endorsed by, or sponsored by Anthropic or any listed publisher. All trademarks belong to their respective owners.