I Built a Bot to Call Eastlink So My Mom Didn't Have To
Brian Stever
July 2023 · Python, Flask, Twilio, Next.js
Abstract. After my mother spent approximately two hours on hold attempting to cancel her Eastlink cable service, I developed an automated telephony system to empirically measure customer service wait times. A Twilio-powered bot calls Eastlink's service line multiple times daily, navigates the IVR phone tree via DTMF tones, and records the duration between entering the hold queue and reaching a human agent. Data collected over several weeks in July 2023 revealed hold times ranging from 3–5 minutes at 08:00 to a peak of 150 minutes at approximately 15:00 Atlantic time. Armed with this data, my mom successfully cancelled her service in under ten minutes.
1.Introduction
In the summer of 2023, my mother called Eastlink, a regional telecom provider in Atlantic Canada, to cancel her cable television subscription. She was placed on hold. She remained on hold for approximately two hours before hanging up, having spoken to no one, cancelled nothing, and lost a meaningful portion of her afternoon.
When she told me about the experience, two things happened in quick succession. The first was empathetic outrage: two hours is an absurd amount of time to wait for any service, let alone the privilege of ending one you're paying for. The second was a quieter, more dangerous thought, familiar to anyone who has ever written a script instead of doing something by hand: I could probably automate that.
Not the cancellation itself. Just the measurement. I wanted to know: was two hours typical, or had my mom drawn the short straw? Was there a best time to call? A worst time? Could you predict hold times with any reliability? The answers, it turned out, were yes, yes, and roughly.
I had prior experience with Twilio's telephony API from an earlier project involving SMS notifications. The conceptual leap from “send a text message” to “make a phone call and sit on hold for an hour” was, in hindsight, not as large as it should have been. What followed was several weeks of building, debugging, accidentally running up a phone bill, and gradually assembling a dataset that nobody at Eastlink would have ever wanted to exist.
2.Methodology
The first task was mapping Eastlink's phone tree. This required calling them manually, which is an experience I can now report is exactly as unpleasant as you would imagine. The Interactive Voice Response (IVR) system is three levels deep. After a bilingual greeting, you press 3 for English. Then 1 for Residential services. Then 1 again for “Cancel or Modify Service,” which is, notably, the last option in the submenu, a design choice I suspect is not accidental.
With the menu structure documented, the next step was teaching a bot to navigate it. Twilio's TwiML markup language allows you to specify DTMF tones (the touch-tone signals generated by pressing buttons on a phone keypad) with precise timing. The bot initiates a call, waits a configurable number of seconds for the greeting to finish, then sends the keypress sequence: 3 → 1 → 1. It does this faster and more reliably than any human, which is perhaps the least impressive thing a computer has ever been better at.
Target: +1 (888) 345-1111
Once past the phone tree, the bot enters the hold queue. Here the methodology relies on a somewhat inelegant observation: the bot cannot speak. It has no voice synthesis, no script, no ability to interact with a human agent. When a customer service representative eventually answers and hears dead silence, they do what anyone would do: say “Hello?” once or twice, wait a few seconds, then hang up. That disconnection event triggers a webhook back to the Flask server, which records the timestamp. The difference between entering the queue and being disconnected equals the hold time.
I am aware this means an Eastlink employee periodically picks up the phone, hears nothing, and adds another entry to whatever internal log they keep for abandoned calls. To the agents who experienced this: I apologize. You were unwitting participants in a study you did not consent to, and your employer's hold times are not your fault.
3.System Architecture
The system comprises three components, connected in a pipeline that runs unattended during business hours. The version preserved in the GitHub repo is slightly plainer than the mythologized version in my head, which is honestly helpful. It consists of a Next.js dashboard, a small Flask service, and a background caller loop that just keeps doing the annoying thing with admirable consistency.
In the committed prototype, the scheduler is not a cron job at all. It is a long-running Flask process that checks the Atlantic time clock, places calls only on weekdays between 08:00 and 18:00, and then sleeps for 15 minutes before trying again. This is less sophisticated than I remembered, but also more in character. Many useful systems are just loops with boundaries.
The Flask backend handles the outbound call and maintains a tiny state object for the front end. The repo version uses Twilio's REST API to initiate the call, waits through a sequence of fixed pauses, then sends the digits 3 → 1 → 1 using inline TwiML. Instead of fancy callback choreography, the script simply polls the call until it completes or times out. Elegant? No. Legible? Extremely.
The prototype keeps recent measurements in memory and exposes them through a single /call_status endpoint. For the longer run, I exported the measurements so the chart had something durable to read. I still did not use a real database. At this scale, introducing one would have been less engineering and more performance art.
The Next.js frontend is the humane layer. It polls the Flask endpoint, renders the most recent status, and plots hold time with Chart.js so another human being does not have to infer a cancellation strategy from raw timestamps. This was the part I built for my mom: a small interface between righteous annoyance and practical action.
- Cron scheduler triggers Flask endpoint (08:00–18:00 AT, Mon–Fri)
- Flask initiates outbound call via Twilio REST API
- TwiML response sends DTMF tones:
3 → 1 → 1 - Bot enters hold queue; timer begins
- Agent answers, hears silence, disconnects
- Twilio sends status callback with call duration
- Duration + timestamp appended to data file
- Frontend polls API and re-renders chart
4.Results
Over the course of approximately four weeks, the bot placed 224 calls during business hours. Of these, 56 resulted in a successful connection to an agent (i.e., the call was answered and subsequently disconnected). The remaining calls were either unanswered (the hold queue timed out after ~180 minutes), or terminated due to Twilio errors, typically network timeouts on calls that exceeded two hours.
The data revealed a clear and consistent diurnal pattern. Calls placed between 08:00 and 09:00 averaged 3–5 minutes of hold time, suggesting that either the call center is fully staffed at open or that nobody else is calling at that hour. Possibly both. Hold times increase steadily through the morning, reaching a plateau of approximately 60–90 minutes between 12:00 and 14:00, before peaking at 127–150 minutes around 15:00. By 17:00, times drop back to roughly 40 minutes as the queue drains before closing.
t (elapsed)
Table 1. Summary statistics from 56 successful call measurements.
| Metric | Value |
|---|---|
| Total calls placed | 224 |
| Successful connections | 56 |
| Optimal call window | 08:00–09:00 |
| Mean hold (08:00–09:00) | 4.2 min |
| Peak hold (observed) | 150 min |
| Peak hour | ~15:00 |
| Twilio cost (total) | $38.47 |
The practical implication was immediate. Armed with four weeks of data, my mother called Eastlink at 08:15 on a Tuesday morning. She was connected to an agent in seven minutes. She cancelled her cable subscription. The whole interaction took less time than it takes to watch a single episode of the television programming she was cancelling.
5.Challenges & Failure Modes
I would like to report that this system worked flawlessly from the first deployment. It did not. The real telephone network, it turns out, is a hostile environment for software that was written in an afternoon and tested primarily against the developer's own voicemail.
- The 3 AM Incident.
- The initial version of the scheduler did not account for time zones. The server was configured in UTC; Eastlink's call center operates on Atlantic time (UTC−3 in summer). For three days, the bot dutifully called Eastlink at 3 AM Atlantic time, reaching a pre-recorded message informing it that the office was closed. Twilio charges per minute regardless of whether a human answers, and the recording loops indefinitely. The bot sat there listening for up to an hour per call before timing out. The resulting bill was several dollars for exactly zero useful data points.
- Per-Minute Billing.
- Twilio's pricing model charges $0.013/minute for outbound calls. This sounds trivial until your bot sits on hold for 150 minutes, at which point a single call costs $1.95. Over 224 calls, the total Twilio bill came to $38.47 — roughly the cost of one month of the cable service my mom was trying to cancel. The irony was not lost on me.
- DTMF Timing.
- The phone tree requires pauses between tones. Send them too fast and the IVR system misses inputs. Too slow and it times out and returns you to the main menu. The correct timing was determined empirically (i.e., I called Eastlink repeatedly and pressed buttons at different speeds until it worked). The final configuration uses 2-second pauses between tones — generous enough to accommodate the IVR's processing time, short enough that I don't pay an extra cent of hold time.
- Agent Detection.
- There is no Twilio API for "a human just picked up the phone." The platform can detect answering machines via cadence analysis, but a customer service agent sounds exactly like a regular human — because they are one. The silent-bot-gets-hung-up-on approach works, but introduces a systematic measurement error of approximately 10–15 seconds (the time between the agent answering and the agent giving up on the silence). For a study measuring hold times in the range of 3–150 minutes, this error was deemed acceptable.
- The Twilio Number.
- After approximately 100 calls from the same number to the same Eastlink line, I began to wonder whether Eastlink's system might flag or block the number. I found no evidence that this occurred, but I also didn't test it rigorously. If you are planning to replicate this study: consider rotating Twilio numbers, or at minimum, accept that your phone number may eventually develop a reputation.
6.Discussion
This is, by any reasonable measure, a disproportionate response to a customer service complaint. My mother wanted to cancel her cable. I built an automated telephony surveillance system, ran it for a month, spent $38.47 in API calls, and presented her with a chart. She could have just called at a random time and gotten lucky. She could have driven to the Eastlink office. She could have, as she eventually pointed out, “just waited on hold like a normal person.”
But the project, despite its obvious over-engineering, revealed something genuinely interesting: telecom hold times are predictable. They follow a clear pattern that repeats daily, and that pattern is knowable to anyone willing to collect the data. Eastlink certainly knows this. They presumably staff their call center based on similar projections. The information asymmetry is that customers don't know it. They call when they call, wait however long they wait, and assume it's random.
It isn't random. Call at 8 AM and you'll wait 5 minutes. Call at 3 PM and you might wait two and a half hours. That's not a scheduling problem; it's a design choice. The friction of cancellation is the product. And measuring it with a bot doesn't change that, but there's a satisfaction in making the invisible visible, even when the thing you're making visible is just a phone queue.
The total cost of the project was $38.47 in Twilio credits, a $5 DigitalOcean droplet for one month, and approximately 20 hours of development time. My mom cancelled her $89/month cable subscription on the first try. If you want to calculate the ROI on that, be my guest, but I suspect the most honest accounting would note that I would have built this regardless of whether it was economically rational. Some projects exist because they should. Most of mine exist because I was curious and slightly annoyed.