Phone number procurement at scale in 2026
Phone number procurement at scale in 2026
if you’re running any operation that depends on SMS verification, outbound messaging, or multi-account workflows, phone numbers are infrastructure. not a nice-to-have. the problem most operators hit is that buying numbers one by one through a dashboard stops working around number 20. you need inventory, you need automation, and you need compliance sorted before your batch gets flagged and recycled.
this guide is for operators running 50+ numbers across campaigns, platforms, or automated workflows. maybe you’re managing social accounts, running SMS marketing for clients, doing outreach at volume, or building a product that provisions numbers for users. the outcome here is a repeatable, API-driven system for procuring, registering, and cycling phone numbers without manually touching a dashboard every time.
by the end you’ll have a working provisioning script, an understanding of the A2P 10DLC compliance requirement that trips up most people in 2026, and a clear picture of what breaks at 10x, 100x, and 1000x scale.
what you need
carrier API accounts - Twilio account with billing enabled. local US long-code numbers run about $1.15/month each plus per-message fees. - Telnyx as a second source. their DID pricing is competitive and the API is nearly identical to Twilio’s. useful for geographic redundancy. - optionally: Bandwidth or SignalWire if you need enterprise volume or want open-source-friendly infrastructure.
compliance prerequisites - a registered US business entity (LLC or corp) if you’re sending A2P SMS to US numbers. no way around this in 2026. - EIN or equivalent tax ID - a real business website with privacy policy and opt-in language. carriers verify this. - Brand registration and Campaign registration under A2P 10DLC, the FCC-backed framework that governs application-to-person messaging in the US.
infrastructure
- Python 3.10+ or Node.js 18+ for the provisioning scripts
- a database (even SQLite works for under 500 numbers) to track number inventory, status, and assignment
- a secrets manager or .env file for API keys. never hardcode.
budget estimate - 100 US numbers: ~$115/month in line rental before usage - Brand registration: one-time $4 fee via Twilio - Campaign registration: $10/month per campaign via Twilio (as of Q1 2026) - expect $200-400/month total for a 100-number operation including light messaging volume
step by step
step 1: verify your carrier account for bulk provisioning
before you touch the API, complete carrier KYC with real business documents. Twilio calls this “business profile” verification. go to console.twilio.com, navigate to Account > Business Profile, and submit your EIN, business address, and authorized representative info.
expected output: verified status within 1-3 business days. unverified accounts hit a hard cap of around 5 numbers.
if it breaks: rejection usually means the name on the EIN doesn’t match your account name exactly. fix the mismatch and resubmit.
step 2: register your A2P brand
in the Twilio console, go to Messaging > Sender Pool > A2P 10DLC. create a new brand. you’ll submit your business name, EIN, vertical (e.g. marketing, account notifications), and website URL.
the TCR (The Campaign Registry) processes this. there’s a one-time $4 fee. standard vetting passes in 24-48 hours. enhanced vetting (required for some verticals) takes 5-7 days and costs more.
expected output: a brand ID like BXXXXXXXX that you’ll attach to campaigns.
if it breaks: if TCR rejects due to “unverifiable business,” your website’s privacy policy is missing or weak. add explicit SMS opt-in language and a contact method, then resubmit.
step 3: create a campaign and get your use case approved
under the same A2P section, create a campaign tied to your brand. you pick a use case (2FA, marketing, customer care, etc.), write sample messages, and confirm opt-in flow details.
marketing campaigns face stricter review than transactional ones. be accurate, not optimistic, about what you’re sending.
expected output: campaign ID like CXXXXXXXX. this unlocks the ability to register numbers to that campaign.
if it breaks: vague sample messages (“hi, check this out”) get rejected. write actual representative copy with your business name and a clear opt-out instruction.
step 4: build the number provisioning script
here’s a minimal Python script using the Twilio SDK to buy and register numbers in batch:
from twilio.rest import Client
import os, time
client = Client(os.environ["TWILIO_SID"], os.environ["TWILIO_TOKEN"])
AREA_CODES = ["415", "212", "312"] # target area codes
CAMPAIGN_SID = os.environ["A2P_CAMPAIGN_SID"]
NUMBERS_TO_BUY = 10
def provision_numbers(area_codes, count, campaign_sid):
purchased = []
for ac in area_codes:
available = client.available_phone_numbers("US").local.list(
area_code=ac, sms_enabled=True, limit=count
)
for number in available[:count]:
bought = client.incoming_phone_numbers.create(
phone_number=number.phone_number,
sms_url="https://your-webhook.example.com/sms"
)
# assign to A2P campaign
client.messaging.v1.services(campaign_sid).phone_numbers.create(
phone_number_sid=bought.sid
)
purchased.append({"sid": bought.sid, "number": bought.phone_number})
time.sleep(0.5) # rate limit headroom
return purchased
numbers = provision_numbers(AREA_CODES, NUMBERS_TO_BUY, CAMPAIGN_SID)
for n in numbers:
print(f"provisioned: {n['number']} ({n['sid']})")
expected output: 10 numbers bought and attached to your campaign. save the SIDs to your database immediately.
if it breaks: 21452 error means the campaign isn’t approved yet. 21421 means you’re trying to buy more numbers than your account tier allows. contact carrier support to raise the limit.
step 5: store inventory in a local database
track every number’s SID, E.164 format, purchase date, assigned campaign, and status. a simple SQLite schema:
CREATE TABLE numbers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sid TEXT UNIQUE NOT NULL,
e164 TEXT UNIQUE NOT NULL,
carrier TEXT DEFAULT 'twilio',
campaign_sid TEXT,
status TEXT DEFAULT 'active',
purchased_at DATETIME DEFAULT CURRENT_TIMESTAMP,
last_used_at DATETIME,
notes TEXT
);
this becomes your source of truth. when you retire numbers or migrate campaigns, update status here first, then make the API call.
expected output: a populated local DB you can query before provisioning new numbers (avoids duplicates).
if it breaks: if your script crashes mid-run, you may have orphaned purchased numbers not in your DB. run a reconciliation query against the carrier API list endpoint to catch these.
step 6: test inbound and outbound routing
before using numbers in production, send a test SMS from each number and confirm delivery. for inbound, hit your webhook URL with a test POST and verify your handler responds with a valid TwiML response.
curl -X POST https://your-webhook.example.com/sms \
-d "From=%2B14155551234" \
-d "To=%2B12125559876" \
-d "Body=test+message"
expected output: 200 OK from your handler, logged message in your system.
if it breaks: 30007 or 30008 error codes from Twilio mean carrier filtering. the number may be on a blocklist from a previous owner. release it and buy another.
step 7: set up a number rotation policy
numbers age. high-volume numbers get flagged by spam filters. build rotation into your workflow from day one:
- track
last_used_atandmessage_countper number - rotate numbers that have sent over 500 messages in 30 days (adjust for your vertical)
- release numbers older than 90 days of inactivity (you’re still paying for them)
def release_stale_numbers(db_conn, carrier_client, days_inactive=90):
cutoff = datetime.now() - timedelta(days=days_inactive)
stale = db_conn.execute(
"SELECT sid FROM numbers WHERE last_used_at < ? AND status='active'",
(cutoff,)
).fetchall()
for row in stale:
carrier_client.incoming_phone_numbers(row["sid"]).delete()
db_conn.execute(
"UPDATE numbers SET status='released' WHERE sid=?", (row["sid"],)
)
db_conn.commit()
return len(stale)
expected output: automatic cleanup keeping your active inventory lean and your bill predictable.
if it breaks: if a number has open conversations or active campaigns, deleting it breaks those flows. always check last_used_at is truly stale, not just a quiet period.
step 8: monitor for carrier filtering and deregistration
set up a daily health check that sends a test message through 5% of your number pool and checks delivery receipts. if a number starts showing undelivered status consistently, decommission it.
also monitor for A2P campaign suspension emails from TCR. they come to your registered email without much warning. a suspended campaign takes all attached numbers offline for outbound SMS.
common pitfalls
skipping A2P registration and hoping for the best. US carriers filter unregistered long-code SMS at the network level in 2026. messages either don’t deliver or hit 30007/30008 errors at high rates. there is no workaround that holds at scale. register properly.
buying all numbers in the same area code. patterns matter to spam filters. distribute across area codes and avoid buying sequential numbers. randomize your provisioning targets.
not tracking number provenance. when a number gets flagged or a campaign suspended, you need to know which numbers are attached to which campaign. operators who keep this in spreadsheets spend hours manually cross-referencing during incidents.
reusing numbers from other operators. recycled numbers carry history. a number that previously belonged to a spam operation will have elevated filtering rates. if you’re seeing high filter rates on freshly bought numbers, buy from a different area code or switch carriers.
ignoring toll-free numbers for high-volume use cases. for transactional SMS at volume, toll-free numbers with toll-free verification can be faster to set up than 10DLC and have different throughput limits. worth benchmarking for your use case. see our toll-free vs long-code comparison at scale for the tradeoff breakdown.
scaling this
10x (100-500 numbers): the script above handles this. main addition is a proper relational DB (Postgres instead of SQLite), campaign segmentation so one suspension doesn’t kill everything, and alerting on delivery rate drops. cost is roughly $500-600/month in line rental before usage.
100x (1000-5000 numbers): you need carrier redundancy. split inventory across Twilio and Telnyx (or Bandwidth). build an abstraction layer in your provisioning code so you can shift numbers between carriers without rewriting downstream integrations. A2P campaigns need to be registered on both carriers separately. a dedicated phone number manager or a lightweight internal SaaS starts making sense here.
1000x (10,000+ numbers): at this volume you’re talking to carrier account managers directly. volume discounts kick in, but so does enhanced compliance scrutiny. you’ll need dedicated short codes for high-throughput campaigns (short codes are $500-1000/month but bypass most filtering). your database needs sharding or a purpose-built telecom inventory system. some operators at this scale build on Bandwidth’s CPaaS API rather than Twilio because of the direct carrier relationships.
for proxy and IP hygiene to go alongside your number operations, the guides at proxyscraping.org/blog/ cover residential and datacenter proxy management at similar scales.
where to go next
- SMS deliverability troubleshooting: diagnosing 30007 and 30008 errors
- Toll-free number verification vs 10DLC: which is right for your use case
- back to the full tutorial index
Written by Xavier Fok
disclosure: this article may contain affiliate links. if you buy through them we may earn a commission at no extra cost to you. verdicts are independent of payouts. last reviewed by Xavier Fok on 2026-05-19.