149 countries · crypto-native · no KYC

Send SMS with PHP in 2026: cURL API Tutorial With Retries

The PHP that runs half the web can send SMS in a dozen lines of cURL. No Composer package required. Here it is with timeouts, safe retries, and a DLR webhook.

$0.035/msg from sub-100ms median 98.6% delivered
Send SMS with PHP in 2026: cURL API Tutorial With Retries — smsroute
$0.004
per SMS from
149
countries
60s
to first message
6
crypto rails
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. 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.

Retries without double-sending — comparison diagram

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

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).

FAQ

How do I send an SMS in PHP?
Use the native cURL extension: POST a JSON body with the recipient and message text to your provider's send endpoint, with a Bearer key pulled from getenv and CURLOPT_TIMEOUT set. No Composer package is required, which suits shared hosting and legacy stacks where most PHP runs.
Do I need a Composer package to send SMS in PHP?
No. The curl extension ships with nearly every PHP install and handles the HTTPS POST an SMS API needs. Skipping the SDK means one less dependency to audit and update, and code that works on hosting where Composer isn't available.
How do I stop PHP SMS retries from double-sending?
Retry only timeouts and 5xx responses, never a successful send or a 4xx. In synchronous request handling, push retries to a queue worker rather than looping inline, so the user's page returns quickly while delivery is reconciled asynchronously via the DLR webhook.
How do I test PHP SMS code without paying per message?
Define a Sender interface and inject a FakeSender that returns a deterministic response offline. Assert on response shape, retry behavior, and idempotent DLR handling, and reserve one real send to a number you control as a pre-release smoke test.

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 →