DEV Community

Muhammad Afzal Kalwar
Muhammad Afzal Kalwar

Posted on

How to Build a Python Auto Dialer: Multi-Line, AMD and Excel Import

To build an auto dialer in Python, you need four parts: a contact importer (Excel/CSV), a dialing engine that drives one or more phone lines, a call-state detector that tells a live answer from voicemail (AMD), and a call log that records every outcome. This guide walks through each part using the design of a working Python auto dialer I built, the Indus Transport Auto Dialer, a Windows desktop app that runs Google Voice lines in the background.

All code below is a simplified sketch to show the idea, not a copy of the production app. The full project is on GitHub: https://github.com/mafzalkalwardev/indus-transport-auto-dialer

What a Python auto dialer actually does

An auto dialer takes a list of numbers and calls them one after another (or several at once), so agents only spend time on calls that connect. The Indus Transport Auto Dialer does this with:

  • Multi-line dialing, with one hidden Google Voice browser per line (PyQt6 QWebEngineView)
  • Automatic sign-in for each line using persistent browser profiles
  • Excel contact import
  • Live status per line: Waiting, Ringing, On call, Voicemail
  • Call timeout and voicemail auto-hangup (AMD)
  • Predictive pacing
  • A local CRM and call history (SQLite)
  • An admin/agent split, where agents only sign in and dial
  • A Windows EXE build

Step 1: Import contacts from Excel

Sales and dispatch teams live in spreadsheets, so Excel import comes first. Normalise phone numbers early, and drop duplicates and blanks before they ever reach the dialer.

import re
import pandas as pd

PHONE_COLUMNS = ["phone", "phone number", "number", "mobile", "cell"]

def load_contacts(path: str) -> list[dict]:
    df = pd.read_excel(path) if path.endswith((".xlsx", ".xls")) else pd.read_csv(path)
    df.columns = [c.strip().lower() for c in df.columns]
    col = next(c for c in df.columns if c in PHONE_COLUMNS)
    contacts, seen = [], set()
    for _, row in df.iterrows():
        digits = re.sub(r"\D", "", str(row[col]))
        if len(digits) == 10:          # US number without country code
            digits = "1" + digits
        if len(digits) < 11 or digits in seen:
            continue
        seen.add(digits)
        contacts.append({"phone": "+" + digits, "name": row.get("name", "")})
    return contacts
Enter fullscreen mode Exit fullscreen mode

Step 2: Model call states explicitly

The biggest source of bugs in a dialer is guessing call state. Make it an explicit state machine so a call can never jump from "ringing" straight to "ended" because of a stale UI banner.

from enum import Enum

class CallState(Enum):
    WAITING = "waiting"
    RINGING = "ringing"
    ON_CALL = "on_call"
    VOICEMAIL = "voicemail"
    NO_ANSWER = "no_answer"
    ENDED = "ended"

ALLOWED = {
    CallState.WAITING: {CallState.RINGING},
    CallState.RINGING: {CallState.ON_CALL, CallState.VOICEMAIL, CallState.NO_ANSWER},
    CallState.ON_CALL: {CallState.VOICEMAIL, CallState.ENDED},
    CallState.VOICEMAIL: {CallState.ENDED},
}

def transition(current: CallState, new: CallState) -> CallState:
    return new if new in ALLOWED.get(current, set()) else current
Enter fullscreen mode Exit fullscreen mode

Step 3: Multi-line dialing

Each line is an independent worker with its own browser session. A simple controller hands the next contact to whichever line is free. Predictive pacing adjusts how many lines dial at once based on how many calls are connecting, so agents aren't left idle or flooded.

import queue, threading

def line_worker(line, contacts: queue.Queue, log):
    while True:
        try:
            contact = contacts.get_nowait()
        except queue.Empty:
            return
        result = line.dial(contact["phone"], timeout=45)   # returns a CallState
        log.record(line.name, contact, result)

def run_campaign(lines, contacts, log):
    q = queue.Queue()
    for c in contacts:
        q.put(c)
    threads = [threading.Thread(target=line_worker, args=(l, q, log)) for l in lines]
    for t in threads: t.start()
    for t in threads: t.join()
Enter fullscreen mode Exit fullscreen mode

Step 4: Voicemail detection (AMD)

Answering machine detection decides whether a human or a voicemail greeting picked up. In a browser-based dialer you combine two signals:

  1. UI cues from the calling page (call timer started, voicemail indicators).
  2. Timing: ignore everything during a short minimum ring window, and only classify voicemail after the call is connected.

When voicemail is confirmed, the dialer hangs up automatically (or plays a recorded message) and moves on. Test AMD carefully with real calls; the project ships a separate AMD and pacing test guide for exactly this reason.

Step 5: Log every call

Every attempt should end in a row you can audit: number, name, line, state, start/end time and duration. A local SQLite database is enough for a single team and doubles as a lightweight CRM.

import sqlite3, datetime as dt

db = sqlite3.connect("crm.sqlite3")
db.execute("""CREATE TABLE IF NOT EXISTS calls
  (phone TEXT, name TEXT, line TEXT, result TEXT, started TEXT)""")

def record(line, contact, result):
    db.execute("INSERT INTO calls VALUES (?,?,?,?,?)",
               (contact["phone"], contact["name"], line, result.value,
                dt.datetime.now().isoformat()))
    db.commit()
Enter fullscreen mode Exit fullscreen mode

Don't want to write it? Use a Chrome extension

If your team already calls from Google Voice in the browser, you may not need a desktop app at all. I also built GV Auto Dialer by INDUS Web Agency, a Manifest V3 Chrome extension that imports CSV/XLSX contacts, auto-dials a queue with a configurable gap, and exports XLSX call reports. Related: IPECS Auto Dialer for iPECS Cloud.

Compliance first

Auto dialers are regulated. In the US, the TCPA and Do Not Call rules apply; in the UK and EU, PECR, Ofcom guidance and GDPR apply. Get consent where required, respect calling hours and DNC lists, and check your phone provider's terms. Software guardrails help, but they are not legal compliance.

FAQ

Can you build an auto dialer in Python?
Yes. Python can import contact lists, drive phone lines through a browser (Selenium or Qt WebEngine) or a telephony API, detect call states and log results.

What is AMD in an auto dialer?
AMD (answering machine detection) decides whether a person or a voicemail greeting answered, so the dialer can hang up or leave a message automatically.

Can a Python auto dialer use Google Voice?
Yes. The Indus Transport Auto Dialer runs a Google Voice session per line in an embedded browser. Check Google's terms for your use case.

How do I import contacts from Excel into an auto dialer?
Read the sheet with pandas, find the phone column, normalise numbers to E.164 format and remove duplicates before dialing.

Where can I hire a remote auto dialer developer?
Muhammad Afzal Kalwar is a freelance auto dialer developer in Pakistan who builds custom Python and Google Voice dialers for US, UK and EU companies: https://mafzalkalwardev.github.io/#hire-me

Is there a ready-made Google Voice auto dialer?
Yes: GV Auto Dialer by INDUS Web Agency on the Chrome Web Store.


About the author

Muhammad Afzal Kalwar is a Python automation developer in Islamabad, Pakistan, and founder of INDUS Web Agency. He builds auto dialers, AI call agents, web scrapers and CRMs for logistics, sales and call-center teams in the US, UK and Europe, and has three auto-dialer Chrome extensions on the Chrome Web Store.

Looking to hire an auto dialer developer? He works remotely with US, UK and EU companies, is available during UK mornings and US afternoons, and quotes fixed-price or hourly in USD, GBP or EUR.

Top comments (0)