Telnyx Logo

Outbound Call Limiter

Smart Call Management with Queue-Based Parallel Processing

What This System Does

The Outbound Call Limiter automatically manages how many times your clubs can call members and guests each day. You control the limits by uploading a simple spreadsheet, and the system handles everything else - processing millions of records in seconds using advanced parallel processing.

✅ Key Benefits:
  • Lightning Fast: Process 1M+ records in 30-60 seconds using parallel queue processing
  • Prevent Over-Calling: Set daily limits per phone number (1, 3, 10, or any number you choose)
  • Compliance: Automatically blocks calls when limits are reached
  • Flexibility: Update limits anytime by uploading a new CSV file
  • Scale: Manage up to 3 million phone numbers efficiently
  • Block Completely: Set limit to 0 to prevent all calls to specific numbers
  • Allow Unlimited: Numbers not in your list can be called freely

📋 How to Use

Simple 3-Step Process

1
Create Your Spreadsheet
Make a CSV file with two columns: Phone numbers and daily call limits
2
Upload to System
Use the upload page to import your data (takes 30-60 seconds even for 1M+ records)
3
That's It!
The system automatically enforces limits on all outbound calls from your clubs
💡 What Happens When Someone Calls:

If the number IS in your CSV:
• System checks how many times it's been called today
• If under the limit → Call goes through normally
• If at the limit → Caller hears: "Sorry, you've reached the maximum number of times you can call this number. Please try again tomorrow."

If the number is NOT in your CSV:
• Call goes through with no restrictions (unlimited calls allowed)

Uploading Your Data

File Format

We're expecting a CSV (comma-separated values) file with two columns:

📋 Required Columns:

1. Phone - The phone number (10 digits, no dashes, spaces, or +1)
2. Limit - The daily call limit for that number (any whole number)

Example: A file with "Phone" as the first column header and "Limit" as the second column header, followed by rows of phone numbers and their corresponding limits.
⚠️ Important Rules:
• Phone numbers must be 10 digits (no dashes, spaces, or +1)
• Limits can be any whole number (1 for guests, 3 for members, or any other value)
• Set limit to 0 to completely block calls to a number
• First row must be headers: Phone,Limit

Two Upload Types

Upload Type When to Use What It Does
Daily (Full Replace) Every morning with your complete list Replaces ALL numbers in the system. Previous data is erased. Use this for your main daily update.
Delta (Update) Throughout the day for changes Updates or adds specific numbers without erasing others. Perfect for mid-day adjustments.

Upload Methods

✅ Web Upload (Recommended for Manual Use)

Use the web interface for manual uploads, testing, or one-off updates.

Open Upload Page →

Features drag-and-drop file upload and visual progress indicators.
💻 API Upload (For Automation)

Best for scheduled daily uploads and automation. Use cURL, Python, or any HTTP client to upload programmatically.

Daily Upload (Full Replace):
curl -X POST "https://outbound-fitness-international.telnyx-worker.dev/outbound/upload?type=daily" -F "file=@/path/to/members.csv"

Delta Upload (Update Only):
curl -X POST "https://outbound-fitness-international.telnyx-worker.dev/outbound/upload?type=delta" -F "file=@/path/to/updates.csv"

Perfect for: Cron jobs, scheduled tasks, CI/CD pipelines, automated workflows
Processing Times:
• Small files (under 1000 rows): 5-10 seconds
• Medium files (10,000-100,000 rows): 10-20 seconds
• Large files (1-3 million rows): 30-60 seconds

How It's So Fast: The system splits your CSV into ~4,000 record chunks and processes them in parallel using Cloudflare Queues. A 1.16M record file becomes 291 chunks that all process simultaneously!

⚙️ How It Works (Technical Overview)

This section is for technical reference. As a customer, you don't need to worry about these details - the system handles everything automatically!

Upload Architecture: Queue-Based Parallel Processing

Why It's Fast & Resource-Efficient:

Instead of processing your CSV file sequentially, we use sophisticated parallel processing with hash-based change detection:

1. Instant CSV Parsing - Your file is parsed and deduplicated in the main worker (takes ~7-10 seconds even for 15MB files)
2. Smart Chunking - Records are split into 4,000-record chunks (~120KB each)
3. Parallel Queue Processing - All chunks are sent to Cloudflare Queues and processed simultaneously by multiple workers
4. Hash-Based Change Detection - Each record gets a hash (phone + limit). Only records with changed hashes are written to the database
5. Database Batching - Changed records are written in 1,000-record batches for optimal performance

Optimization: For hourly 1M+ delta uploads where most records don't change, hash-based detection skips 90%+ of database writes, drastically reducing resource usage and cost!

Example: A 1.16M record file becomes 291 chunks that all process at the same time. If 95% of records are unchanged, only 58K records are actually written - reducing total processing time to 30-60 seconds!

What Happens During Upload

1
File Received
Main worker receives your CSV file (max 15MB)
2
Parsing & Deduplication
CSV is parsed and duplicate phone numbers are removed (keeps last occurrence). Takes ~7-10 seconds for 1M+ records.
3
Chunking
Records are split into 4,000-record chunks. Example: 1.16M records → 291 chunks.
4
Queue Batching
Chunks are sent to Cloudflare Queue in batches of 2 messages at a time (to stay under 256KB batch limit). Takes ~15 seconds for 291 messages.
5
Parallel Processing
Queue consumers (up to 10 concurrent workers) process chunks simultaneously. Each chunk:
  • First chunk in Daily upload: Purges all existing data
  • Writes records to database in 1,000-record batches
  • Last chunk: Marks upload as "completed"
6
Complete
All chunks finish processing in 30-60 seconds total. Your data is ready!

Technical Constraints & Solutions

Constraint Limit Our Solution
Worker CPU Time 30 seconds (paid) Parse CSV quickly (~10s), send to queue (~15s). Total: 25s ✓
Queue Message Size 128KB per message 4,000 records = ~120KB ✓
Queue Batch Size 256KB total Send 2 messages per batch (240KB) ✓
Queue sendBatch Limit 100 messages Send in batches of 2, loop until done ✓
Database Operations 1,000 per batch Process chunks in 1,000-record batches ✓

System Reliability

✅ Built for Scale:
• Parallel processing: Up to 10 queue consumers running simultaneously
• Automatic retries: Failed chunks retry up to 3 times
• Dead letter queue: Permanently failed messages are captured for review
• 99.9% uptime: Runs on Cloudflare's global network
• Real-time status: Check upload progress via API endpoint
Monitoring Your Upload:

When you upload a file, you receive an upload_history_id. Use this to check status:

GET /outbound/upload/status?id={upload_history_id}

Returns:
• status: "pending" (queued), "processing" (in progress), or "completed"
• records_processed: Total records in CSV
• records_added/updated: How many records were written
• Storage information and timestamps

Daily vs Delta Upload Behavior

Daily Upload (Full Replace):

What happens:
• First chunk: Deletes ALL existing records from database
• All chunks: Insert fresh records with counter = 0
• Last chunk: Marks upload as "completed"

Use case: Morning refresh to give everyone a clean slate for the new day
Delta Upload (Selective Update):

What happens:
• No purge! Existing data is preserved
• For each phone: Check if it exists in database
• If exists: Update limit only (preserve current counter)
• If new: Insert with counter = 0

Use case: Mid-day adjustments without affecting existing call counters

API Endpoints

Upload CSV

POST /outbound/upload?type={daily|delta} Parameters: • type: "daily" (full replace) or "delta" (update only) Body: • Content-Type: multipart/form-data • Field: "file" (CSV file) Response (202 Accepted): { "success": true, "message": "Upload received. Processing 291 chunks in parallel via queue...", "upload_id": "550e8400-e29b-41d4-a716-446655440000", "upload_history_id": 15, "upload_type": "daily", "filename": "daily-2025-01-07T10-30-00.csv", "upload_time": "25.32s", "total_records": 1160103, "chunks": 291, "status_endpoint": "/outbound/upload/status?id=15" }

Check Call Limit

GET /outbound/check?phone={phone_number} Response (allowed): { "phone": "5551234567", "allowed": true, "call_limit": 3, "current_count": 1, "remaining": 2 } Response (blocked): { "phone": "5551234567", "allowed": false, "call_limit": 3, "current_count": 3, "remaining": 0 } Note: Counter automatically resets at midnight EST (first call after midnight)

Check Upload Status

GET /outbound/upload/status?id={upload_history_id} Response: { "id": 15, "status": "completed", // or "pending" "upload_type": "daily", "filename": "members.csv", "storage_filename": "daily-2025-01-07T10-30-00.csv", "records_processed": 1160103, "records_added": 1160103, "records_updated": 0, "uploaded_at": "2025-01-07T10:30:00Z", "completed_at": "2025-01-07T10:31:15Z" }

Get Upload History

GET /outbound/upload/history Response: { "success": true, "count": 10, "uploads": [ { "id": 15, "upload_type": "daily", "filename": "members.csv", "records_processed": 1160103, "uploaded_at": "2025-01-07T10:30:00Z" }, ... ] }

Get Statistics

GET /outbound/stats Response: { "total_numbers": 1160103, "at_limit": 42, "average_limit": 2 }

Reset Call Counters

POST /outbound/reset Option 1: Reset ALL numbers { "reset_all": true } Response: { "success": true, "message": "All counts reset to 0", "reset_date": "2025-11-07", "numbers_reset": 1160103 } Option 2: Reset specific numbers (array) { "phones": [ "555-123-4567", "+1 (555) 987-6543", "5551234567" ] } Response: { "success": true, "message": "Reset 3 phone number(s)", "reset_date": "2025-11-07", "requested": 3, "normalized": 3, "numbers_reset": 3 } Notes: • Counters automatically reset at midnight EST • Phone numbers are normalized (handles various formats) • Use either reset_all OR phones array (not both)