Sender Number Pool Architecture

How DropDialer's BYOC + Drop Cowboy outbound delivery flow actually works

TL;DR — Critical Architecture Fact

The phone numbers we assign to customers are NOT just inbound callback routing. Under our BYOC (Bring Your Own Carrier) setup via Twilio, those numbers serve as the actual outbound caller ID shown on the prospect's phone when they receive a voicemail. Drop Cowboy orchestrates the drop, but the call physically originates through our Twilio SIP trunk, and Twilio enforces that the caller ID is a number on our master account. The pool must scale with paid signups, and every number in PricingConfig.package_allocations is a real, hard deliverable backed by real Twilio inventory.

The Actual Outbound Flow

  1. Customer launches campaign in DropDialer (via createCampaign,sendOneOffDrop, or processScheduledDrops).
  2. Backend reads customer's assigned PhoneNumber records from the PhoneNumber entity, filtered by assigned_to_email and excluding source: 'byon' (BYON numbers are customer-owned and not on our master Twilio account — using them as caller ID causes Twilio Error 32204).
  3. Per-prospect rotation — backend rotates through the customer's assigned numbers using rotationIndex % senderNumbers.length. Each prospect gets a different caller ID for warm-up + reputation distribution.
  4. POST to Drop Cowboy /v1/rvm with payload including forwarding_number set to the rotated Twilio number (in E.164 format).
  5. Drop Cowboy initiates the call through our Twilio SIP trunk (BYOC). Twilio places the call. The number we passed as forwarding_number is what appears as caller ID on the prospect's phone.
  6. If the prospect calls back, the call rings the same Twilio number, hits twilioVoiceWebhook, and is forwarded to the customer's personal phone via the number's forwarding_number field on the PhoneNumber record.

Why the Drop Cowboy API Naming Is Misleading

Drop Cowboy's forwarding_number parameter sounds like it's only for inbound forwarding. But under BYOC, the same number serves two purposes:

  • Outbound caller ID — what the prospect sees when their phone rings (Twilio enforces this).
  • Inbound callback target — when the prospect calls that number back, it rings the customer.

This is why a customer's plan tier must come with a real, dedicated set of Twilio numbers. The "dedicated sender numbers included" marketing claim on the Checkout page is literal infrastructure, not just an inbound feature.

What Pool Inventory Means in Practice

Counts toward pool

  • PhoneNumber records with source: 'twilio'
  • Provisioned via provisionInboundNumbers
  • Live on our master Twilio account

Does NOT count

  • BYON numbers (source: 'byon') — customer-owned, can't be caller ID
  • Drop Cowboy's own shared pool — DC does NOT supply the caller ID
  • Numbers in paused or burned health status

Pool Sizing Math (Per Paid Signup)

Source of truth: PricingConfig.package_allocations[plan].phone_numbers. Defaults (as of May 2026):

PlanNumbers per SignupDrops/moNotes
Free Trial1100Low warm-up; single number
Starter2750Light rotation
Growth41,500Multi-number reputation balancing
Pro63,000High volume requires distribution
Elite159,000"Massive sender pool for warm-up & rotation"

Code Paths That Touch the Pool

  • functions/createCampaign — rotates assigned numbers as caller ID per prospect (lines 687-710, 755-763)
  • functions/sendOneOffDrop — same rotation logic for one-off drops
  • functions/processScheduledDrops — same rotation logic for scheduled batches
  • functions/claimPoolNumbers — assigns available pool numbers to a customer at signup, respecting PricingConfig allocation
  • functions/changeStripePlan — provisions additional numbers when a customer upgrades
  • functions/provisionInboundNumbers — admin tool to purchase new Twilio numbers into the available pool
  • functions/checkSenderPoolHealth — daily automation that alerts admin when available pool runs low
  • functions/senderHealthCheck — daily automation that scores number reputation and auto-pauses burned numbers

Common Failure Modes

Twilio Error 32204

Caller ID rejected — happens if a BYON number is passed as forwarding_number. Filter source: 'byon' at every drop site.

Pool depletion at signup

An Elite signup needs 15 free numbers. If pool has fewer, claimPoolNumbers assigns what's available — customer is underprovisioned. checkSenderPoolHealth alerts on low inventory.

Burned number reputation

If a number's 7-day success rate drops below threshold, senderHealthCheck sets health_status: 'burned' and paused_at. That customer is now sending from a smaller effective pool until replacement.

Document history: Written May 22, 2026 after architectural deep-dive during the PricingConfig single-source-of-truth refactor. Resolves prior confusion where the forwarding_number field in DC's API was misinterpreted as inbound-only. The double-duty (caller ID + callback target) was confirmed by code review of createCampaign.js lines 686-710 and the BYON exclusion comment referencing Twilio Error 32204.