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)