curl
extension ships with virtually every PHP install, so there is no Composer package to add, audit, or
update. That matters on the shared hosting and legacy stacks where a lot of PHP still lives. We call
SMSRoute endpoints here (SMSRoute is a no-KYC SMS API with crypto billing: BTC, ETH, USDT, XMR, LTC, and SOL. So
first send is minutes), but any provider swaps in at the URL.
Native cURL, no Composer needed
How to send SMS with PHP using native cURL without Composer?
SMSRoute's REST API works with PHP's native cURL. No Composer or SDK required. Just a single POST request with your API key, recipient, and message. This keeps your project lightweight and dependency-free, ideal for quick integrations or legacy systems.
To send SMS with PHP takes one cURL call. The curl extension ships with virtually
every PHP install, so there is no Composer package to add, audit, or update. We call SMSRoute endpoints here
(SMSRoute is a no-KYC SMS API with crypto billing: BTC, ETH, USDT, XMR, LTC, and SOL. So first send is minutes), but any provider swaps in at the URL.
<?php
function send_sms(string $to, string $text): array {
$ch = curl_init('https://api.smsroute.cc/sms/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('SMSROUTE_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['to' => $to, 'from' => 'YourBrand', 'message' => $text]),
]);
$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code >= 300) throw new RuntimeException("send failed: $code");
return json_decode($body, true); // generic response
}
The key comes from getenv, never a literal in source or a config file in the webroot. And
CURLOPT_TIMEOUT is set. Without it, a slow provider hangs the whole page render, which for a login
form is a worse failure than a clean error. The recipient number should be in international format (E.164) to
avoid carrier filtering. For more on E.164 formatting, see the ITU-T recommendation. For example, set `export
SMSROUTE_KEY="your-api-key-here"` in your server's `.env` file or shell profile before starting the
application.
Retries without double-sending
How to retry SMS sending in PHP without double-sending messages?
SMSRoute's API uses idempotency keys. Include a unique key in each request. If a retry is needed, the same key ensures the message is sent only once. This prevents duplicate charges and keeps your delivery logic simple and reliable.
Retry only failures where the message did not send: timeouts and 5xx. Never retry a success or a 4xx. Use an idempotency key to prevent double-sending across retries. Include the `X-Idempotency-Key` header with a unique UUID value in your API request. The server checks this key to ignore duplicate submissions.
function send_with_retry(string $to, string $text, int $tries = 3): array {
for ($i = 0; $i < $tries; $i++) {
try {
return send_sms($to, $text);
} catch (RuntimeException $e) {
$is5xx = preg_match('/send failed: 5\d\d/', $e->getMessage());
if (!$is5xx || $i === $tries - 1) throw $e;
usleep(500000 * (2 ** $i)); // 0.5s, 1s, 2s
}
}
}
| Failure | Retry? | Why |
|---|---|---|
| Timeout / network | Yes, backoff | May never have reached the provider |
| 5xx | Yes, backoff | Provider-side, before acceptance |
| 4xx (bad number, auth, balance) | No | Fix the request; retry re-fails |
| Success | Never | Message sent; retry double-sends |
In a synchronous PHP request, do not retry OTP sends inline for long — push retries to a queue worker (or cap at ~4 seconds) so the user's request returns fast, then reconcile via DLR.
The DLR webhook endpoint
How to set up a DLR webhook endpoint for SMS delivery status in PHP?
SMSRoute sends real-time delivery reports to your webhook URL. In PHP, create an endpoint that receives POST data with status, message ID, and timestamp. This lets you track delivery success, failures, and update your database instantly. No polling needed.
A queued response is acceptance, not delivery. The delivery receipt lands later on a webhook you expose. For A2P
traffic, carriers often require registration (e.g., 10DLC in the US or DLT in India) to ensure high delivery
rates. See the CTIA's A2P 10DLC guidelines for US requirements.
<?php // dlr.php: provider POSTs delivery updates here
$dlr = json_decode(file_get_contents('php://input'), true);
mark_delivery($dlr['id'], $dlr['status']); // idempotent, keyed on id
http_response_code(200); // ack immediately
- Return 200 fast, process afterward. Slow endpoints get callbacks retried or dropped.
- Key updates on the message id. Duplicate DLRs are normal, so the update must be idempotent.
- Guard ordering. A late
sentmust not overwrite adelivered; track state, not last write. - Store timestamps to compute real latency per destination, per the OTP latency method.
Testing without spending
How to test SMS sending in PHP without spending real money?
SMSRoute gives free test credits on signup, no credit card required. Use these to verify your PHP cURL integration, test retry logic, and confirm DLR webhooks work. Only fund your account when you're ready to send to real numbers.
interface Sender { public function send(string $to, string $text): array; }
class FakeSender implements Sender {
public function send(string $to, string $text): array {
return ['id' => 'test_123', 'status' => 'queued']; // free, offline
}
}
Production costs are per message and destination. Model them with the international cost guide before you launch. SMSRoute's published route pages list delivery from $0.004/message (premium direct-carrier corridors up to $0.035) with sub-100ms median submission and ~98.6% delivered success (smsroute.cc route pages, 2026).
Related reading
FAQ
How do I send an SMS in PHP?
Do I need a Composer package to send SMS in PHP?
How do I stop PHP SMS retries from double-sending?
How do I test PHP SMS code without paying per message?
Send your first SMS in 5 minutes
No KYC. Pay with BTC, ETH, USDT, XMR, LTC, and SOL. Live routes to 149 countries.
Get an API key →