
Bring Your Own SIM: Android SMS Gateways in Production and What Breaks
Dual SIM quirks, batch pacing, async delivery states, battery optimization, and inbound gaps: an operations guide for teams sending SMS from their own phones.
TL;DR
- Running SMS through your own phone works, but you now operate hardware. Budget for five things: a dedicated device on a charger, one unambiguous SIM, an async delivery model, battery optimization exemptions, and a reconciliation sweep.
- The highest-return change is a spare Android on permanent power with battery optimization off. Most "the API is broken" reports are a phone put to sleep.
- HTTP 200 does not mean delivered. A send is queued:
pending, thendispatched,sent,delivered. - Throughput is bounded by the phone and the carrier: hundreds to low thousands of messages per day per device, not hundreds per second.
- Go hybrid when it fits. Your own SIM for transactional sends, a cloud provider for marketing bursts, short codes, or an SLA.
"Use your phone as an SMS gateway" sounds simple: HTTP in, SMS out. In production the friction is never the HTTP call. It is device state, carrier behaviour, and a mental model borrowed from cloud SMS APIs that does not fit a phone. You send from your own number with no per-message fee, and in exchange uptime is your phone's uptime. For the architecture overview, read what an Android SMS gateway is. This post is the operations half.
What breaks #1: dual SIM ambiguity
Two lines in one phone is the most common first-week failure. Sends leave from the unexpected SIM, inbound automations attach to the wrong line, and the other SIM's carrier traffic mixes into your feed.
How to detect it: send a test message and see which number the recipient gets. Repeat after each OS update.
Mitigations. Standardize on one SIM per gateway phone, and label the device. If you need dual SIM, do not rely on the Android default: textbee takes simSubscriptionId on the send call, and the app lists each SIM's subscription id under Dashboard, in SIM Cards.
{
"recipients": ["+15550142"],
"message": "Your code is 481920",
"deviceId": "665f1c2a9b1e4a0012ab34cd",
"simSubscriptionId": 2
}One sharp edge: simSubscriptionId is not validated. If the id does not match a SIM in the phone it is ignored and the message goes out from the preferred or default SIM. Nothing fails, so if the sending number matters, confirm which number the message arrived from. deviceId is the opposite: a malformed id fails the request rather than falling back, so a typo can never silently send from the wrong phone.
What breaks #2: batch pacing and carrier tolerance
Queues build up. A cron job fires a few hundred messages at once, or a device returns from an outage with a backlog. A phone emptying that queue as fast as it can looks, to a carrier, like a spam script.
Carriers filter aggressively on the consumer messaging path. CTIA's Messaging Principles and Best Practices (May 2023) describes the industry's use of filtering software, machine learning, and analytics against unwanted traffic, and separates consumer messaging from application-to-person traffic. A consumer SIM sending like an A2P campaign is the pattern those systems look for.
Symptoms: the first few dozen messages deliver, then delivery stops while the device still reports them sent. Behaviour normalizes after a quiet period.
What textbee does for you. Large sends are not dumped on the phone. The server releases them in waves paced to the device's smsSendDelaySeconds setting. Queued messages carry a dispatchDueAt timestamp, and a large send returns an estimatedCompletionAt:
{
"data": {
"success": true,
"message": "SMS added to queue for processing",
"smsBatchId": "665f1c2a9b1e4a0012ab34cf",
"recipientCount": 420,
"estimatedCompletionAt": "2026-09-23T14:05:00.000Z"
}
}That estimate holds only if the device stays online. Treat it as a plan, not a promise.
What you should do. Raise smsSendDelaySeconds on the device rather than adding sleeps in your own code, so pacing survives retries and restarts. Spread large lists across the day, and vary message bodies where the content allows it.
What breaks #3: an HTTP 200 is not a delivered message
A 200 from POST /gateway/send-sms means the batch was accepted and queued. Several hops remain, each able to fail on its own: the server queue, the push to the device, SmsManager handing off to the carrier, the carrier network, the recipient's handset. textbee tracks that with a lowercase status on every message (pending, dispatched, sent, delivered, failed, unknown) and a timestamp for each transition, and since October 2026 every batch carries a status and per-state counts of its own. The delivery status docs walk through every state and the three ways to track them.
Two things to internalize. sent is the strongest confirmation a SIM-based gateway can give on its own, because the device is confirming the hand-off. And status=sent is not the same axis as direction=sent: one is a delivery state, the other means outbound. The history endpoint accepts both, which is why direction=sent&status=failed is the query for "which of my sends failed".
Mitigations. Model delivery as asynchronous wherever it touches your product. If a user-facing action waits on an SMS, add a timeout and a fallback, and never take an irreversible action on the strength of the initial 200. Subscribe to the MESSAGE_SENT, MESSAGE_DELIVERED, and MESSAGE_FAILED webhook events so state changes reach you as they happen.
What breaks #4: battery optimization and silent stalls
Android kills background work to save power, and several OEMs layer more aggressive schedulers on top of Doze. The symptoms are distinctive: queued messages all flush the moment the screen turns on, inbound SMS go missing overnight, heartbeats stop after the phone sits idle.
The fix is two settings and a charger: keep the device on permanent power, set the textbee app to Unrestricted battery, and disable the manufacturer's sleep and autostart restrictions. The app's Device health screen (Settings in the Android app) checks each of these and shows the steps for your phone brand. Per-brand paths for Samsung, Xiaomi, Huawei, Oppo, OnePlus and Vivo are in Android SMS gateway not sending? Fix battery optimization.
Do not confuse this with a permission problem. If sends fail immediately rather than stalling, the cause is usually the SMS permission, not the scheduler. The dashboard now shows an SMS permission off badge on the device as soon as the phone reports it, before the first message fails. Android 15 and 16 made that permission stricter for sideloaded apps; the Android 15 permission guide covers the fix.
What breaks #5: inbound capture gaps
Two-way flows (reply handling, STOP keywords, OTP confirmation) fail quietly when inbound capture is not solid.
Check the boring things. Receiving is off by default: turn it on in the app, then confirm it under Devices in the dashboard or read receiveSMSEnabled from the device list endpoint. RECEIVE_SMS is granted separately from SEND_SMS. Webhook URLs must be public HTTPS: private and loopback hosts are rejected, so local development needs a tunnel.
Then close the gap. A webhook unreachable for ten minutes is ten minutes of lost inbound messages, and nothing in your logs will say so. The fix is a reconciliation sweep: walk GET /gateway/messages with direction=received, order=asc, and a cursor, then process anything your handler never saw. Cursor pagination makes that safe to run on a loop. The webhook is the fast path, the sweep is the proof. The inbound webhook guide covers the endpoint side, including signature verification.
What breaks #6: expecting cloud volume from a phone
The most expensive mistake is a planning mistake. A team tests at a handful of messages a day, launches, then pushes a marketing blast through one SIM. The device sends for a while, hits carrier filtering, and the rest arrive hours late.
Be honest about the ceiling. One phone on one consumer SIM handles hundreds to low thousands of messages per day, best spread out. That suits transactional traffic: codes, reminders, alerts, notifications. It does not suit carrier-grade bulk marketing, and no tuning changes that.
Plan limits are the other half. Checked October 2026 on the pricing page: the free plan allows one active device, 50 messages per day and 300 per month. Pro is $14.99 per month ($8.33 a month billed yearly), allows up to 5 active devices, and raises the cap to 5,000 messages per month with no daily limit. Scale is $29.99 per month ($16.67 billed yearly) for 15 devices and 25,000 messages. The monthly allowance resets on the day your plan started, not on a rolling 30-day window, so a launch-day burst does not haunt the next four weeks.
Mitigation: test at several times your expected launch-day volume, and add devices early. A second phone buys headroom, a second sender number, and a second carrier so one outage is not total. Note what it is not: there is no automatic load balancer. A send goes to the deviceId you name, or to your default device, or to the enabled device with the most recent heartbeat. Routing is your application's job, usually a small map from message type to device id plus a fallback.
What to monitor in production
A healthy gateway is boring. Alert on the things that make it stop being boring:
- Device reachability.
lastHeartbeaton the device list endpoint is the honest signal. Alert on silence, not failure.batteryInfoandnetworkInfoon the same object tell you why. - Device health, on the phone. The app's Device health screen checks permissions, notifications, battery usage, power saving and the connection to textbee, and the Activity tab shows each message from the moment the phone got it to delivery. Check both before blaming the carrier.
- Queue age. If
pendingmessages accumulate ordispatchDueAtkeeps sliding, the phone is not draining. A device problem, not a carrier one. - Failure rate. Track
direction=sent&status=failedover a rolling window. A spike usually means a deactivated SIM, no signal, or a permission lost to an OS update. - The
senttodeliveredratio. Messages that reachsentand stop are the fingerprint of carrier filtering. - Inbound liveness. A message from a second phone, due at your webhook within a minute, beats any dashboard check.
The production readiness checklist
- Dedicated device on permanent power, not a daily phone. A cheap secondhand Android is enough.
- Battery optimization disabled for the textbee app.
-
SEND_SMSandRECEIVE_SMSgranted, re-confirmed after every OS update. - Receiving enabled in the app if you use inbound, verified via
receiveSMSEnabled. -
smsSendDelaySecondsset to a pace you have tested on your carrier. - SIM, number, and owner documented. Who swaps the SIM, who reboots the phone.
- Heartbeat alert for device silence, and status webhooks plus a cursor sweep as the backstop.
- Runbook written: power, signal, permissions, plan limit, carrier balance.
- Compliance reviewed for your message types: SMS compliance checklist.
Hybrid: your own SIM plus a cloud provider
Most teams past a certain size run both.
| Your own SIM (textbee) | Cloud SMS provider | |
|---|---|---|
| Best for | Transactional: codes, reminders, alerts, replies | Marketing bursts, global reach, short codes |
| Sender | Your own number, already known to recipients | A provisioned number or short code |
| Throughput ceiling | Set by the phone and the carrier | Set by your plan |
| Per-message fee | None, beyond your normal SMS plan | Per message, per country |
| Operational load | You run a phone | The provider runs the fleet |
Use the cloud provider for SLAs, short codes, burst throughput, or countries your SIM does not cover. Use your own SIM for the transactional slice, which for most products is the majority of sends. To run the backend on your own infrastructure too, see self-hosting an open source SMS gateway.
Frequently asked questions
How do I tell carrier throttling apart from a device problem?
Look at where messages stop. If they reach sent but never delivered, the device did its job and the block is downstream. If they sit at pending or dispatched, the phone is the problem: check power, battery optimization, signal, SEND_SMS.
What happens if the phone loses power mid-batch?
Messages already accepted stay queued server-side. When the device reconnects and reports a heartbeat, the queue resumes. Nothing is dropped, but the delay is real, so time-sensitive messages need a fallback.
Should I use a prepaid SIM?
Prepaid works, and many teams prefer it because there is no contract to unwind if a line gets suspended. Read the plan's terms on automated and commercial messaging first, and keep a spare activated SIM ready.
When should I stop using my own SIM?
When your volume, reach, or contracts outgrow a phone. Sustained bulk marketing, many countries, short codes, or a customer contract with an uptime SLA all say move that traffic to a cloud provider. Keeping transactional traffic on your own number while you do it is usually right.
Get more textbee in Google Search
Add textbee.dev as a preferred source. Our posts show up more often in your Google results.
You may also like

How to Poll for New SMS Without Duplicates or Gaps
Page-number polling drops and repeats messages when new SMS arrive mid-read. Use textbee's cursor pagination to read every message exactly once.

What Is an Android SMS Gateway? How It Works, Pros, Cons & When to Use One (2026)
An Android SMS gateway turns a phone you already own into a programmable SMS sender. Learn how it works, when it beats Twilio, real costs, and setup steps.

Open Source SMS Gateway: Self-Host Your Own with an Android Phone (2026)
textbee is an open source (AGPLv3), self-hostable SMS gateway. What open source means here, self-hosted vs managed, how to run it with Docker Compose, and how it compares with Gammu and other Android gateways.