How to Build a Private SMS Gateway with an Idle Android Phone & HTTP API in 30 Minutes
Transform an idle Android phone into a zero-cost SMS gateway using the open-source httpSMS project. This step-by-step tutorial covers API setup, Spring Boot integration for sending verification codes, and Webhook configuration for handling inbound messages.

How to Build a Private SMS Gateway with an Idle Android Phone & HTTP API in 30 Minutes
Backend developers have all encountered this scenario: your system needs to send SMS verification codes, IoT devices require alert notifications, or you want a server script to notify you when something breaks. Third-party SMS providers often require ICP filing (in China), credit card binding (overseas), and charge per message, which adds up quickly. Using physical SMS modems is expensive and maintenance-heavy.
What if you could solve this using just an idle Android phone?
Today, we'll walk through a practical implementation using httpsms, an open-source project that turns your Android device into a fully functional SMS gateway. It exposes a clean HTTP API, allowing your backend to send messages and receive inbound SMS via Webhooks. By the end of this guide, you'll have a zero-cost, self-managed private SMS service.
Prerequisites
Before we dive in, ensure you have the following:
- An idle or primary Android phone (Android 5.0+, SIM slot, functional SMS capability)
- A SIM card with SMS enabled (preload a small balance; domestic SMS typically costs a few cents)
- A backend project (I'll demonstrate with Java/Spring Boot, but the logic applies to any language)
- A publicly accessible domain or tunneling tool (e.g., ngrok, required for Webhook callbacks)
Basic requirements: familiarity with HTTP APIs and basic backend development. We'll use the official managed service to get up and running in under 10 minutes without self-hosting servers.
Step 1: Register & Get Your API Key
Visit httpsms.com and register an account (email supported). Once logged in, navigate to the Settings page to locate your API Key. This key acts as your primary credential for all API calls, similar to cloud provider AccessKeys. Keep it secure.
Why is this Key necessary? The httpSMS backend API is stateless. It relies on the API Key to authenticate requests and route them to your bound Android device. A single Key covers all devices linked to your account.
Step 2: Install & Configure the Android App
This is the only hardware interaction required in the entire workflow.
- Download and install the httpSMS APK from the official GitHub Releases or direct download link.
- Open the app and log in using the account you just registered.
- Grant the requested permissions during setup:
- SMS Access: Used to read delivery reports and intercept inbound messages for forwarding.
- Notification Access: Receives push notifications to trigger sending actions.
- Battery Optimization Whitelist: Prevents Android's power management from killing the background service.
⚠️ Pitfall Alert: Custom Android ROMs (MIUI, ColorOS, HarmonyOS) aggressively terminate background processes. After installation, navigate to Settings → Battery → App Launch Management and explicitly allow background activity and auto-start for httpSMS. Failing to do this will cause messages to fail or callbacks to drop silently.
Once logged in, you'll see an "Online" status indicator. Your phone is now bound to the API and ready for scheduling.
Step 3: Send Your First SMS
The API is intentionally minimal with one core endpoint. Let's test it using curl:
bash
curl -X POST "https://api.httpsms.com/v1/messages/send" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "This is a test SMS from httpSMS, sent at '$(date '+%Y-%m-%d %H:%M:%S')'",
"from": "+8613800138000",
"to": "+8613900139000"
}'
Replace YOUR_API_KEY with your actual key, from with your bound phone number, and to with the recipient. You'll receive a 202 Accepted response. This means the request has been queued, and your Android device will dispatch the actual SMS shortly.
Why
202instead of200? httpSMS uses an asynchronous architecture: the API acknowledges receipt immediately, pushes a silent notification via Firebase to your phone, the device fetches the payload, and finally sends it via Android's native SMS API. This decouples the API layer from device connectivity. Temporary offline phones will not block or timeout your backend requests.
Step 4: Real-World Implementation – Spring Boot SMS Verification Service
Sending a test message is straightforward, but let's integrate it into a practical flow. Assume you have a user registration endpoint that requires SMS-based verification.
4.1 Project Dependencies
In your Spring Boot project, add WebClient for reactive HTTP calls. No custom SDKs are required:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
4.2 SMS Service Layer
java
@Service
public class HttpSmsService {
private static final String API_BASE = "https://api.httpsms.com/v1";
private final WebClient webClient;
@Value("${httpsms.api-key}")
private String apiKey;
@Value("${httpsms.phone-number}")
private String phoneNumber; // e.g., +8613800138000
public HttpSmsService(WebClient.Builder builder) {
this.webClient = builder
.baseUrl(API_BASE)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader("x-api-key", apiKey)
.build();
}
/**
* Send SMS verification code
* @param targetPhone Recipient's phone number
* @param code 4~6 digit verification code
*/
public Mono<Void> sendVerificationCode(String targetPhone, String code) {
String content = String.format("[YourApp] Your verification code is %s. Valid for 5 minutes.", code);
Map<String, String> body = Map.of(
"content", content,
"from", phoneNumber,
"to", targetPhone
);
return webClient.post()
.uri("/messages/send")
.bodyValue(body)
.retrieve()
.bodyToMono(JsonNode.class)
.doOnSuccess(r -> log.info("SMS submitted. Target: {}, Response: {}", targetPhone, r))
.then();
}
}
4.3 Controller & Configuration
java
@RestController
@RequestMapping("/api")
public class UserController {
private final HttpSmsService smsService;
private final StringRedisTemplate redisTemplate;
// Constructor injection omitted for brevity...
@PostMapping("/register/send-code")
public ResponseEntity<?> sendCode(@RequestParam String phone) {
String code = String.valueOf(ThreadLocalRandom.current().nextInt(100000, 999999));
// Store code in Redis, expires in 5 mins
redisTemplate.opsForValue().set("sms:code:" + phone, code, 5, TimeUnit.MINUTES);
smsService.sendVerificationCode(phone, code)
.subscribe(); // Async send, non-blocking response
return ResponseEntity.ok(Map.of("message", "Verification code sent"));
}
}
Configure credentials in application.yml:
yaml
httpsms:
api-key: "YOUR_API_KEY_HERE"
phone-number: "+8613800138000"
Start the project and call /api/register/send-code?phone=+86150xxxx1234. Your idle phone will immediately dispatch the verification code. The entire flow runs through your own infrastructure without third-party SMS platform dependencies.
Step 5: Receive SMS – Webhook Callback Handling
Beyond outbound messages, httpSMS supports forwarding inbound SMS to your server via Webhooks. This is highly useful for scenarios like opt-out replies (e.g., replying TD or STOP), keyword-triggered actions, or interactive bot logic.
5.1 Configure the Webhook
In the httpsms.com Settings page, locate the Webhook URL field and enter your server's callback endpoint, for example:
https://your-domain.com/api/sms/callback
Local Development Tip: Use
ngrok http 8080to expose a temporary public tunnel. Set the Webhook URL tohttps://xxxx.ngrok-free.app/api/sms/callback.
5.2 Callback Handler Code
java
@PostMapping("/sms/callback")
public ResponseEntity<?> handleSmsWebhook(@RequestBody Map<String, Object> payload) {
log.info("Received SMS callback: {}", payload);
String from = (String) payload.get("from");
String content = (String) payload.get("content");
// Example: User replies 'TD' to unsubscribe
if ("TD".equalsIgnoreCase(content.trim())) {
log.info("User {} requested unsubscribe", from);
// Execute business logic...
}
return ResponseEntity.ok(Map.of("status", "received"));
}
httpSMS will POST to this URL immediately upon receiving a new SMS on your Android device. The payload includes from, to, and content. Ensure your endpoint reliably returns a 200 OK status.
Retry Mechanism: Non-2xx responses trigger automatic retries. Implement idempotency in your callback handler to prevent duplicate processing.
FAQ & Troubleshooting
-
Message fails or stays pending
- Check the app's internal logs. Verify if the status is
sent,pending, orexpired. - Ensure mobile data is active and Firebase Push notifications are enabled.
- Double-check battery optimization and auto-start permissions.
- Check the app's internal logs. Verify if the status is
-
Webhook not triggering
- Verify the Webhook URL is publicly accessible (use ngrok for local testing).
- Ensure your endpoint returns
200. Repeated 4xx/5xx responses will exhaust retries and drop the message. - Review the webhook delivery logs in the Settings dashboard for precise error tracking.
-
Handling Long SMS Messages
- Android's native SMS API has a character limit (~70 characters for Chinese). Longer messages are automatically split. httpSMS handles this segmentation transparently, but keeping messages concise helps control cellular costs.
-
Cost Structure
- The official managed service includes a generous free tier. Overage is billed on a pay-as-you-go basis. Refer to the official website for current pricing.
- Self-hosting via Docker is fully supported. See the official documentation for deployment guides.
Summary
In this tutorial, we built a complete private SMS gateway from scratch using just an Android phone and the httpSMS API. Let's recap the workflow:
- Register an account and secure your API Key.
- Install the app, log in, and disable aggressive battery optimization.
- Call
/v1/messages/sendvia HTTP to dispatch outbound SMS. - Configure a Webhook URL to route inbound messages to your backend.
This architecture eliminates the need for complex middleware, regulatory filings, or tedious carrier API integrations. It's ideal for MVPs, internal notification systems, IoT alerts, and personal automation projects.
What's next?
- Integrate official SDKs: Go SDK or Node SDK
- Combine Webhooks with CI/CD pipelines: Receive SMS → Trigger deployment scripts
- Explore rate limiting (Back Pressure) and message expiration features for production-grade reliability
Have questions or run into issues? Drop a comment below or open an issue on the repository. Happy coding, and see you in the next guide!