After SMS-Activate closed in December 2025, I tested five SMS activate alternatives to automate phone verification in E2E tests. Code examples and honest results.
Last October, our QA pipeline broke at 2 AM. The reason? My personal phone number — the one hardcoded into 14 different Playwright specs — got rate-limited by Twilio's verification flow. Every test that touched 2FA went red. PagerDuty woke me up, and I spent the next hour manually rotating a number from a prepaid SIM I kept in my desk drawer.
I'd been meaning to set up a proper virtual number provider for months. We had an SMS-Activate account that one of our QA guys used for manual testing, but I never got around to integrating its API into the pipeline. Then, on December 29, SMS-Activate shut down entirely — and suddenly I had no choice but to find a real solution.
This post is about the five SMS-Activate alternatives I tested, what worked, what didn't, and the setup we ended up with.
The Problem Nobody Talks About
If you've ever built anything with phone-based auth, you know the pain. You need real phone numbers that:
- accept OTP codes from services like Google, Telegram, or your own Twilio/Vonage setup
- can be provisioned programmatically (no human in the loop)
- don't get flagged as VoIP by strict verification systems
- cost less than the mass of prepaid SIMs gathering dust in your office
Our stack is a Django REST backend + Next.js frontend, deployed via GitHub Actions. We run ~200 E2E tests per push to main, and about 30 of them involve SMS-based flows: sign-up, password reset, 2FA toggle, phone number change. Each one needs a unique, working phone number.
I spent roughly three weeks testing different SMS-Activate alternatives. Here's what I found.
What I Was Looking For
Before I get into specific services, here's the criteria I used. This isn't a generic "best tools" list — these are the things that mattered for our CI/CD context:
API-first. If I can't call it from a shell script or a pytest fixture, it doesn't exist for me. I need to request a number, poll for an incoming SMS, and release the number — all via HTTP.
Number freshness. Reused numbers are the #1 cause of failed verifications. If the number was already used to register a Google account last week, it'll get rejected. I need services that cycle through large pools.
Country coverage. We test geo-specific onboarding flows for US, DE, ID, and BR users. I need numbers from at least these four countries.
Cost at scale. At 30 tests × ~5 CI runs per day × 22 workdays, that's 3,300 numbers/month. Price per number matters a lot at this volume.
Speed. If SMS delivery takes more than 20 seconds, my test times out. I set a hard ceiling of 15s for OTP receipt.
My Testing Setup
Here's the simplified pytest fixture I ended up with. The actual implementation varies per provider, but the interface is the same:
# conftest.py
import httpx
import time
class SMSProvider:
"""Abstract base for virtual number providers."""
def get_number(self, country: str, service: str) -> dict:
raise NotImplementedError
def wait_for_code(self, activation_id: str, timeout: int = 15) -> str:
raise NotImplementedError
def release(self, activation_id: str) -> None:
raise NotImplementedError
@pytest.fixture
def phone_number(sms_provider):
"""Provision a temporary number, yield it, then release."""
result = sms_provider.get_number(country="US", service="google")
yield result["phone"], result["activation_id"]
sms_provider.release(result["activation_id"])
With this abstraction, switching providers is a one-line config change. Which came in handy — because my first choice didn't work out.
The Five Services I Actually Tested
I originally tried seven, but two had APIs that returned 500s on the first call, so I'm not going to waste your time with those. Here are the five that at least functioned.
1. HeroSMS
Site: hero-sms.com
This ended up being our daily driver, so I'll go deeper here.
I found HeroSMS while digging through a Telegram group where people discuss automation tooling. The API is straightforward — REST with JSON responses, no OAuth dance, just an API key in the header.
What convinced me to keep testing it past the trial phase:
Number pool size. They claim 500K+ fresh numbers daily, and in practice, I've never hit a "no numbers available" wall, even for Google verifications from US numbers at peak hours. Over three months of daily CI runs, our success rate for OTP receipt sits at 94.7% — the remaining ~5% are timeouts on the service side (Google being slow), not HeroSMS failing to deliver.
Pricing at our volume. Numbers start at fractions of a cent. For our mix of US/DE/ID/BR numbers across Google, Telegram, and our own app, the average cost came out to ~$0.03/verification. At 3,300/month, that's under $100/month for the entire test suite. Before this, we were spending ~$40/month on prepaid SIMs and still running out.
API response time. Number provisioning takes 1–3 seconds. SMS delivery averages 4–8 seconds in my logs. Well within the 15-second budget.
Here's the wrapper I wrote:
class HeroSMSProvider(SMSProvider):
BASE = "https://hero-sms.com/api"
def __init__(self, api_key: str):
self.client = httpx.Client(
headers={"Authorization": f"Bearer {api_key}"},
timeout=30,
)
def get_number(self, country: str, service: str) -> dict:
resp = self.client.get(f"{self.BASE}/get-number", params={
"country": country,
"service": service,
})
resp.raise_for_status()
data = resp.json()
return {
"phone": data["number"],
"activation_id": data["id"],
}
def wait_for_code(self, activation_id: str, timeout: int = 15) -> str:
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
resp = self.client.get(f"{self.BASE}/get-status", params={
"id": activation_id,
})
data = resp.json()
if data.get("code"):
return data["code"]
time.sleep(2)
raise TimeoutError(f"No OTP received within {timeout}s")
def release(self, activation_id: str) -> None:
self.client.post(f"{self.BASE}/cancel", json={
"id": activation_id,
})
The catch: payments are crypto-only. We had to set up a company crypto wallet just for this, which was a minor bureaucratic headache. If your finance team is allergic to crypto, this might be a dealbreaker. For us, it was a 30-minute setup and then we forgot about it.
2. SMSPool
Site: smspool.net
SMSPool was my second choice and the one I recommend if you need non-VoIP numbers specifically. Some services (looking at you, Google Voice and Cash App) reject VoIP numbers outright. SMSPool has a pool of real-SIM-backed numbers that pass these checks.
The API is decent — not as fast as HeroSMS, but reliable. Average OTP delivery was 8–12 seconds in my tests. They accept Stripe payments, which made our finance team happier.
Where it fell short for us: number availability for Indonesian numbers was inconsistent. Some days we'd get them instantly, other days the queue would timeout. For US and DE numbers, no issues.
Good for: teams that primarily need US/EU non-VoIP numbers and want to pay with a regular credit card.
3. 5SIM
Site: 5sim.net
5SIM is the cheapest option I tested — numbers from $0.008. The coverage is wide (180+ countries), and for low-volume or manual testing, it's genuinely fine.
The problem showed up at scale. Out of 500 test runs over two weeks, about 12% of numbers failed to receive OTPs. That's significantly higher than what I saw with HeroSMS (5%) or SMSPool (7%). The failures were inconsistent — sometimes it was a US Google verification, sometimes a German Telegram code. Hard to debug, harder to trust in CI.
I still keep a 5SIM balance for ad-hoc manual testing when I need a number from an unusual country (say, Bangladesh or Peru). It's great for that. I wouldn't run my pipeline on it.
Good for: manual testing, exploratory work, tight budgets.
4. SMS-Man
Site: sms-man.com
SMS-Man has the broadest service catalog I've seen — 1,000+ supported services. If you need to verify some obscure Chinese app or a niche social network, they probably have it.
The API works, the Telegram bot is a nice touch for quick manual grabs, and the pricing is reasonable ($0.05+). But for my use case (automated CI), the speed wasn't there. Average OTP delivery was 10–15 seconds, and about 8% of requests hit my timeout ceiling. The free shared numbers are a cool idea for quick one-off tests but obviously not suitable for anything serious.
Good for: wide service coverage, manual testing via Telegram bot, migrating from SMS-Activate (similar workflow).
5. Veritel
Site: veritel.io
Veritel's selling point is that every number is backed by a physical SIM card connected to actual hardware. This means near-carrier-grade reliability — and their stats back it up. In my tests, OTP delivery success was 96%, the highest of any service I tried.
The trade-off is price. At ~$0.10+ per number, Veritel is roughly 3x more expensive than HeroSMS for our volume. At 3,300 numbers/month, that's ~$330 vs ~$100. We couldn't justify the difference given that HeroSMS was already clearing our success threshold.
I'd use Veritel for a specific scenario: if you're testing against a platform that's extremely aggressive about rejecting virtual numbers (think: banking apps, fintech verification), Veritel's physical SIM approach is worth the premium.
Good for: fintech/banking verification testing, platforms that reject VoIP aggressively.
What We Settled On
HeroSMS runs our daily CI. SMSPool is our fallback for the rare case when we need a guaranteed non-VoIP US number. 5SIM sits in my personal toolkit for ad-hoc exploration.
The total monthly cost went from "$40/month on SIMs that still didn't cover our test matrix" to "~$100/month with full automation and 95% success rates." More importantly, no one has been woken up at 2 AM because a hardcoded phone number got rate-limited.
Practical Tips If You're Setting This Up
Don't hardcode the provider. Use the abstraction pattern from my fixture above. You will switch providers at some point — make it painless.
Set a timeout and retry with a different number. Not every number works every time. My fixture has a retry wrapper that requests a new number if OTP doesn't arrive within 15 seconds. Two retries max, then fail the test.
Track success rates. I log every verification attempt with provider, country, service, time-to-OTP, and success/failure. This data is what told me HeroSMS was outperforming the others — gut feeling alone would've kept me on 5SIM because it's cheaper.
Use country-specific numbers for geo-tests. Don't just grab "any available number." If your test checks that a German user sees the German onboarding flow, use a DE number. It matters for some services' fraud detection.
# In your CI config (e.g., .github/workflows/e2e.yml)
env:
SMS_PROVIDER: herosms
SMS_API_KEY: ${{ secrets.HEROSMS_API_KEY }}
SMS_FALLBACK_PROVIDER: smspool
SMS_FALLBACK_API_KEY: ${{ secrets.SMSPOOL_API_KEY }}
Wrapping Up
The SMS-Activate shutdown caught a lot of people off guard, but honestly, the market for SMS-Activate alternatives in 2026 is more competitive and mature than what we had before. Services now compete on API reliability and delivery speed, not just price.
Virtual number services aren't glamorous tooling. Nobody's giving a conference talk about their SMS verification pipeline. But if you ship anything with phone-based auth — and in 2026, that's most of us — having a reliable, automated verification flow in your tests is the kind of infrastructure investment that pays for itself the first time it prevents a broken release.
If you've solved this differently — maybe with a mock layer, or a self-hosted SMS gateway — I'd be curious to hear about it. My approach works, but I'm sure there are better ones.
The code examples above are simplified for readability. In production, add proper error handling, rate limiting on your side, and secret management. Don't commit API keys to your repo — use your CI platform's secret store.
Top comments (0)