
Send SMS from PHP: No Twilio, Just Your Android Phone
Send real SMS from PHP using textbee and your Android phone as the gateway. Native cURL, Guzzle, and Laravel Http examples: zero per-message fees.
TL;DR
- textbee's REST API works from any PHP version that can make HTTP requests: no SDK needed.
- Three options: native cURL (zero dependencies), Guzzle (
composer require guzzlehttp/guzzle), or Laravel'sHttpfacade (one line). - Store credentials in environment variables, not hardcoded strings.
- The
recipientsfield is an array: one request can reach multiple numbers. - No per-message API fee: messages go through the SIM in your Android phone.
You don't need a Twilio account or a virtual phone number to send SMS from PHP. If you have an Android phone with a working SIM, textbee turns it into an SMS gateway your PHP code can call over a simple REST API. The message goes out from your real phone number.
Before you start
- textbee app installed on your Android device and linked to your account: download and quickstart
- Device ID and API key from the dashboard at textbee.dev
- PHP 7.4+ (the examples use modern syntax, but the cURL approach works on older versions too)
- On Android 15 or 16? You may need to manually enable SMS permissions once
Option 1: Native cURL (no dependencies)
This works on any PHP installation. No Composer, no packages.
<?php
function sendSms(string $to, string $message): array
{
$deviceId = getenv('TEXTBEE_DEVICE_ID');
$apiKey = getenv('TEXTBEE_API_KEY');
$url = "https://api.textbee.dev/api/v1/gateway/devices/{$deviceId}/send-sms";
$body = json_encode(['recipients' => [$to], 'message' => $message]);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
"x-api-key: {$apiKey}",
],
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status < 200 || $status >= 300) {
throw new RuntimeException("SMS send failed with HTTP {$status}: {$response}");
}
return json_decode($response, true);
}
// Usage
$result = sendSms('+15551234567', 'Hello from PHP!');
print_r($result);Export your credentials before running:
export TEXTBEE_DEVICE_ID="your-device-id"
export TEXTBEE_API_KEY="your-api-key"
php send.phpOption 2: Guzzle HTTP
If your project already uses Guzzle (or you prefer a more expressive HTTP client):
composer require guzzlehttp/guzzle<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
function sendSms(string $to, string $message): array
{
$deviceId = getenv('TEXTBEE_DEVICE_ID');
$apiKey = getenv('TEXTBEE_API_KEY');
$client = new Client(['base_uri' => 'https://api.textbee.dev']);
try {
$response = $client->post("/api/v1/gateway/devices/{$deviceId}/send-sms", [
'headers' => [
'x-api-key' => $apiKey,
'Content-Type' => 'application/json',
],
'json' => [
'recipients' => [$to],
'message' => $message,
],
]);
return json_decode($response->getBody()->getContents(), true);
} catch (RequestException $e) {
$body = $e->hasResponse() ? $e->getResponse()->getBody()->getContents() : '';
throw new RuntimeException("SMS send failed: {$body}", 0, $e);
}
}
$result = sendSms('+15551234567', 'Hello from Guzzle!');
print_r($result);Option 3: Laravel Http facade
If you're in a Laravel project, this is the cleanest path:
use Illuminate\Support\Facades\Http;
function sendSms(string $to, string $message): array
{
$response = Http::withHeaders([
'x-api-key' => config('services.textbee.api_key'),
])->post(
'https://api.textbee.dev/api/v1/gateway/devices/' . config('services.textbee.device_id') . '/send-sms',
[
'recipients' => [$to],
'message' => $message,
]
);
$response->throw(); // throws on 4xx/5xx
return $response->json();
}Add your credentials to config/services.php:
'textbee' => [
'api_key' => env('TEXTBEE_API_KEY'),
'device_id' => env('TEXTBEE_DEVICE_ID'),
],And in .env:
TEXTBEE_API_KEY=your-api-key
TEXTBEE_DEVICE_ID=your-device-idWhat the API returns
On success, you get a JSON response wrapped in data:
{
"data": {
"success": true,
"message": "SMS added to queue for processing",
"smsBatchId": "abc123xyz",
"recipientCount": 1
}
}success: true means the message was accepted and queued on your device: not yet necessarily delivered to the recipient's handset. The device sends it asynchronously over the cellular network. Track delivery status in the textbee dashboard, or configure webhooks to get notified when status changes.
On failure, you get a non-2xx status code and a body like:
{
"success": false,
"error": "Device not found or not active"
}Common causes: wrong device ID, API key mismatch, device offline, or SMS permissions revoked on the Android device.
Sending to multiple recipients
The recipients field is an array. Send one request to reach multiple numbers:
$result = sendSms(['+15551234567', '+15559876543'], 'Appointment reminder: tomorrow at 10am.');
// Adjust the function signature:
function sendSms(array|string $to, string $message): array
{
$recipients = is_array($to) ? $to : [$to];
// ... rest of the function, use $recipients in the JSON body
}Things worth knowing
Phone number format: Use E.164 format (+ followed by country code and number, no spaces or dashes). Example: +15551234567 for a US number. Some carriers accept local formats for same-country sends, but E.164 is unambiguous and always correct.
No per-message API fee: Because messages route through your own Android SIM, you pay your carrier's SMS rate, which is often included in an unlimited plan. The textbee pricing is a flat monthly subscription, not per-message.
Opt-in matters: Messages come from your real phone number. Unsolicited texts can violate regulations regardless of which SMS technology you use. Read the compliance checklist before sending to lists.
Rate limits: The practical constraint is your SIM and carrier throughput (~hundreds of messages per hour on a consumer SIM), not an API rate limit. For higher volume, connect multiple Android devices to the same account.
Frequently asked questions
Does this work with Laravel?
Yes. Option 3 above uses Laravel's Http facade directly. For a full Laravel integration with queued jobs, see the dedicated Laravel SMS guide.
Do I need to install any packages?
No. Option 1 uses PHP's built-in curl extension, which is enabled by default on virtually every PHP installation. Options 2 and 3 require Guzzle/Laravel, but only if you prefer their interface.
Can I send to multiple numbers in one call?
Yes. Pass an array to recipients. One API call, one batch: each number gets the same message body.
What PHP version does this require?
Option 1 works on PHP 5.6+ (drop the type hints for older versions). Options 2 and 3 require PHP 7.4+ and their respective dependencies.
Is there a textbee PHP SDK?
Not yet. The REST API is simple enough that a raw HTTP call is all you need: typically 10-15 lines. The API docs cover all endpoints if you need inbound webhooks, device status, or batch operations beyond what's shown here.
Keep going
- Full API reference: send, receive, webhooks, device management
- How to send SMS programmatically: curl, Python, and JavaScript side by side
- SMS compliance checklist: before you send to a list
- Pricing: free tier available, paid plans start small
Download textbee and send your first PHP SMS in under 5 minutes.
You may also like

textbee Version 2.7.0: Enhanced Device Management, Scheduled SMS, and Improved Reliability
Discover the latest features in textbee version 2.7.0, including custom device names, SIM card selection, scheduled SMS sending, and improved reliability.

Cost-Effective SMS Solutions: Why textbee Beats Traditional SMS APIs in 2026
Traditional SMS APIs charge per segment, carrier fees, number rental, and 10DLC. An Android SMS gateway like textbee replaces all that with one flat fee.

SMS Compliance Checklist: TCPA, Opt-In, and Best Practices
A practical SMS compliance checklist for US marketers and developers. TCPA, consent, opt-out, and how to send compliant SMS campaigns.