Przejdź do treści
SMS API

Wysyłanie SMS w PHP, Pythonie i Node.js: gotowe przykłady z własnym numerem

Jak wysłać SMS w PHP, Pythonie i Node.js przez API z własnego numeru: gotowy kod, obsługa błędów, ponawianie i sprawdzanie statusu dostarczenia.

Zespół smsportalOpublikowano: 9 min czytania
Przewodnik API w panelu smsportal z przykładami kodu

Jeśli szukasz sposobu na wysyłanie SMS w PHP, Pythonie lub Node.js bez płacenia za każdą wiadomość, API smsportal przyjmuje zwykłe żądanie HTTP i wysyła SMS z Twojego numeru przez podłączony telefon z Androidem. Poniżej gotowe funkcje w trzech językach: wysyłka, obsługa błędów, ponawianie i sprawdzanie statusu. Konfigurację telefonu i klucza opisuje pełny przewodnik po SMS API, tu skupiamy się na kodzie.

Co jest potrzebne do wysłania SMS z kodu?

Potrzebujesz klucza API i podłączonego telefonu. Cała reszta to jedno żądanie POST. Adres bazowy to https://smsportal.app/api/v1, a klucz idzie w nagłówku x-api-key.

  1. Załóż konto w smsportal i połącz telefon z Androidem (kod QR).
  2. W panelu wygeneruj klucz API.
  3. Zapisz go w zmiennej środowiskowej:
bash
export SMSPORTAL_API_KEY="wklej_tutaj_klucz"
Panel smsportal z urządzeniami i kluczami API
Panel smsportal z urządzeniami i kluczami API

Uwaga: nie ma oficjalnej paczki SDK na npm, Packagist ani PyPI. Używamy zwykłego HTTP. Dzięki temu nie dokładasz zależności, a przykłady działają w każdej wersji języka.

Jak wygląda minimalne żądanie w cURL?

Najkrótsza wersja to jedno polecenie curl. Pozwala sprawdzić klucz i telefon, zanim napiszesz kod.

bash
curl -X POST "https://smsportal.app/api/v1/gateway/send-sms" \
  -H "x-api-key: $SMSPORTAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"recipients": ["+48600100200"], "message": "Test z terminala"}'

Odpowiedź 200 zawiera data.smsBatchId. Ten identyfikator posłuży do sprawdzenia statusu. Numery podawaj w formacie międzynarodowym (+48...).

Jak wysłać SMS w PHP?

W PHP użyj rozszerzenia cURL. Poniższa funkcja wysyła SMS, sprawdza kod HTTP i zwraca smsBatchId albo rzuca wyjątek z treścią błędu.

php
<?php
const SMSPORTAL_API = 'https://smsportal.app/api/v1';

function sendSms(array $recipients, string $message, ?string $scheduledAt = null): string
{
    $payload = ['recipients' => $recipients, 'message' => $message];
    if ($scheduledAt !== null) {
        $payload['scheduledAt'] = $scheduledAt; // ISO 8601, w przyszłości
    }

    $ch = curl_init(SMSPORTAL_API . '/gateway/send-sms');
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 15,
        CURLOPT_HTTPHEADER => [
            'x-api-key: ' . getenv('SMSPORTAL_API_KEY'),
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS => json_encode($payload),
    ]);

    $body = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $error = curl_error($ch);
    curl_close($ch);

    if ($body === false) {
        throw new RuntimeException("Błąd sieci: $error");
    }
    if ($status !== 200) {
        throw new RuntimeException("HTTP $status: $body", $status);
    }

    return json_decode($body, true)['data']['smsBatchId'];
}

$batchId = sendSms(['+48600100200'], 'Samochod gotowy do odbioru. Warsztat Kowalski.');
echo $batchId;

Dla PHP nie musisz instalować Guzzle. Jeśli jednak już go używasz, ten sam schemat sprowadza się do $client->post($url, ['headers' => [...], 'json' => [...]]).

Jak wysłać SMS w Pythonie?

W Pythonie najwygodniejsza jest biblioteka requests (pip install requests). Funkcja niżej ma timeout, kontrolę kodu odpowiedzi i ponawianie błędów chwilowych.

python
import os
import time
import requests

API = "https://smsportal.app/api/v1"
HEADERS = {"x-api-key": os.environ["SMSPORTAL_API_KEY"]}


def send_sms(recipients, message, scheduled_at=None, retries=3):
    payload = {"recipients": recipients, "message": message}
    if scheduled_at:
        payload["scheduledAt"] = scheduled_at  # ISO 8601, w przyszłości

    for attempt in range(retries):
        try:
            res = requests.post(
                f"{API}/gateway/send-sms",
                headers=HEADERS, json=payload, timeout=15,
            )
        except requests.RequestException:
            time.sleep(2 ** attempt)           # błąd sieci: ponów
            continue

        if res.status_code == 200:
            return res.json()["data"]["smsBatchId"]
        if res.status_code >= 500:
            time.sleep(2 ** attempt)           # błąd serwera: ponów
            continue
        # 400, 401, 429: ponawianie nic nie zmieni
        raise RuntimeError(f"HTTP {res.status_code}: {res.text}")

    raise RuntimeError("Nie udało się wysłać SMS po kilku próbach")


batch_id = send_sms(["+48600100200"], "Wizyta jutro o 10:00. Odpisz TAK.")
print(batch_id)

Dla FastAPI czy Django wywołanie wrzuć do zadania w tle (Celery, RQ), żeby żądanie użytkownika nie czekało na sieć.

Jak wysłać SMS w Node.js?

W Node.js 18 i nowszym masz wbudowany fetch, więc nie instalujesz niczego. Poniższa funkcja ma ten sam kontrakt co wersje powyżej.

javascript
const API = 'https://smsportal.app/api/v1'
const headers = {
  'x-api-key': process.env.SMSPORTAL_API_KEY,
  'Content-Type': 'application/json',
}
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))

export async function sendSms(recipients, message, scheduledAt, retries = 3) {
  const payload = { recipients, message, ...(scheduledAt && { scheduledAt }) }

  for (let attempt = 0; attempt < retries; attempt++) {
    let res
    try {
      res = await fetch(`${API}/gateway/send-sms`, {
        method: 'POST',
        headers,
        body: JSON.stringify(payload),
        signal: AbortSignal.timeout(15000),
      })
    } catch {
      await sleep(2 ** attempt * 1000) // błąd sieci: ponów
      continue
    }

    if (res.ok) return (await res.json()).data.smsBatchId
    if (res.status >= 500) {
      await sleep(2 ** attempt * 1000) // błąd serwera: ponów
      continue
    }
    // 400, 401, 429: ponawianie nic nie zmieni
    throw new Error(`HTTP ${res.status}: ${await res.text()}`)
  }
  throw new Error('Nie udało się wysłać SMS po kilku próbach')
}

console.log(await sendSms(['+48600100200'], 'Zamowienie 1042 wyslane.'))

Które błędy ponawiać, a których nie?

Ponawiaj tylko błędy chwilowe: brak połączenia, timeout i kody 5xx. Pozostałe wymagają poprawki po Twojej stronie. Ślepe ponawianie 400 lub 429 nic nie da, a przy timeoucie grozi duplikatem SMS-a.

KodZnaczeniePonawiać?
200Zlecenie przyjęteNie, zapisz smsBatchId
400Zły numer lub treść, brak włączonego urządzenia, zły scheduledAtNie, popraw dane
401Błędny lub unieważniony kluczNie, sprawdź x-api-key
429Wyczerpany limit planu (dzienny, miesięczny lub jednorazowy)Nie od razu
5xx, timeoutBłąd serwera lub sieciTak, z rosnącą przerwą

Wskazówka: po timeoucie żądanie mogło jednak dojść do serwera. Zanim ponowisz, sprawdź GET /gateway/messages?search=... albo listę z ostatnich minut, żeby klient nie dostał dwóch identycznych SMS-ów.

Jak sprawdzić status SMS-a w kodzie?

Użyj GET /gateway/messages z smsBatchId zwróconym przy wysyłce. Pole status przechodzi z pending przez dispatched i sent do delivered albo failed.

python
import os
import requests

res = requests.get(
    "https://smsportal.app/api/v1/gateway/messages",
    headers={"x-api-key": os.environ["SMSPORTAL_API_KEY"]},
    params={"smsBatchId": batch_id},
    timeout=15,
)
for m in res.json()["data"]:
    print(m["recipient"], m["status"])

To samo w PHP i Node.js to zwykły GET z parametrem w adresie:

javascript
const url = new URL('https://smsportal.app/api/v1/gateway/messages')
url.searchParams.set('smsBatchId', batchId)

const res = await fetch(url, { headers: { 'x-api-key': process.env.SMSPORTAL_API_KEY } })
const { data } = await res.json()
data.forEach((m) => console.log(m.recipient, m.status))

W panelu te wartości widać jako etykiety: w kolejce (pending i dispatched), wysłana (sent), dostarczona (delivered) i nieudana (failed). Dodanie status=failed zwraca tylko wiadomości nieudane z danej partii. Status sent oznacza, że telefon przekazał SMS do sieci, a delivered dopiero potwierdzenie operatora. Nie wszystkie sieci zwracają potwierdzenia, więc brak delivered nie zawsze znaczy błąd.

Oś czasu wiadomości w aplikacji: zlecona, wysłana, dostarczona

W produkcji nie odpytuj API w pętli. Skonfiguruj webhook na zdarzenia MESSAGE_SENT, MESSAGE_DELIVERED i MESSAGE_FAILED, a serwer dostanie powiadomienie od razu. Weryfikację podpisu X-Signature pokazuje artykuł odbieranie SMS przez API i webhook.

Jak wysłać wiele wiadomości i zaplanować wysyłkę?

Dla wielu odbiorców z różnym tekstem użyj POST /gateway/send-bulk-sms, a dla późniejszej godziny dodaj scheduledAt. W obu przypadkach schemat żądania zostaje ten sam.

javascript
const res = await fetch('https://smsportal.app/api/v1/gateway/send-bulk-sms', {
  method: 'POST',
  headers,
  body: JSON.stringify({
    messages: [
      { recipients: ['+48600100200'], message: 'Anna, wizyta jutro o 10:00.' },
      { recipients: ['+48601200300'], message: 'Piotr, wizyta jutro o 11:30.' },
    ],
  }),
})

W telefonie z dwiema kartami SIM dodaj simSubscriptionId, by wybrać kartę. Bez niego używana jest domyślna karta telefonu. Wysyłkę z pliku CSV ze zmiennymi opisuje artykuł o masowej wysyłce SMS z CSV lub Excela.

Ile SMS-ów na minutę wyśle skrypt?

Skrypt może wysłać żądanie szybko, ale telefon wysyła wiadomości z przerwą, domyślnie 5 sekund. To około 12 SMS-ów na minutę z jednego telefonu. Serwer ustawia większe partie w kolejce i wypuszcza je falami dopasowanymi do tej przerwy.

Nie ma sensu odpalać tysięcy żądań w równoległych wątkach. Dla większego wolumenu podłącz kilka telefonów. Do kampanii masowych na tysiące odbiorców lepsza jest hurtowa bramka SMS. Polskie litery skracają segment z 160 do 70 znaków (kodowanie UCS-2), więc w kodzie warto pilnować długości tekstu.

Jak wpiąć wysyłkę w Laravel, Django i Express?

Wywołanie API wydziel do jednej klasy lub funkcji i uruchamiaj z kolejki zadań, nie z kontrolera. Dzięki temu timeout sieci nie blokuje odpowiedzi użytkownikowi, a ponowienia robi worker.

Laravel (klient HTTP wbudowany we framework, zadanie w kolejce):

php
<?php
use Illuminate\Support\Facades\Http;

class SendSmsJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable;
    public int $tries = 3;
    public function backoff(): array { return [5, 30, 120]; }

    public function __construct(private array $to, private string $text) {}

    public function handle(): void
    {
        $res = Http::withHeaders(['x-api-key' => config('services.smsportal.key')])
            ->timeout(15)
            ->post('https://smsportal.app/api/v1/gateway/send-sms', [
                'recipients' => $this->to,
                'message' => $this->text,
            ]);

        if ($res->serverError()) { $res->throw(); }          // ponów
        if ($res->failed()) { $this->fail(new Exception($res->body())); } // nie ponawiaj
    }
}

Django (zadanie Celery z funkcją send_sms z wcześniejszego przykładu):

python
from celery import shared_task

@shared_task(bind=True, max_retries=3)
def send_sms_task(self, recipients, message):
    try:
        return send_sms(recipients, message)
    except RuntimeError as exc:
        raise self.retry(exc=exc, countdown=30)

Express (endpoint, który odpowiada od razu, a wysyła w tle):

javascript
app.post('/orders/:id/notify', async (req, res) => {
  res.status(202).json({ queued: true })
  sendSms([req.body.phone], `Zamowienie ${req.params.id} wyslane.`)
    .catch((err) => console.error('SMS nie wyszedł:', err.message))
})

Uwaga: przy produkcyjnych wysyłkach zapisuj smsBatchId w bazie przy zamówieniu lub wizycie. Gdy klient zapyta „czy dostałem SMS?”, odpowiesz jednym zapytaniem.

Jak przetestować integrację przed wdrożeniem?

Testuj w trzech krokach: pierwsze żądanie na własny numer, test błędów i test z wyłączonym telefonem. Dzięki temu zobaczysz każdy status, zanim zobaczy go klient.

  1. Na własny numer. Wyślij wiadomość do siebie i sprawdź w aplikacji oś czasu: zlecona, w kolejce, wysłana, dostarczona.
  2. Zły klucz i pusta treść. Podmień x-api-key na losowy ciąg (oczekuj 401), a potem wyślij pusty message (oczekuj 400). Upewnij się, że kod nie ponawia tych błędów.
  3. Telefon offline. Wyłącz internet w telefonie, wyślij SMS i obserwuj status pending. Po włączeniu internetu powinien przejść do sent.

W testach automatycznych nie wysyłaj prawdziwych SMS-ów. Podmień funkcję sendSms na atrapę (mock) i sprawdzaj, że dostaje poprawny numer i treść, a w testach „wyjątków” zwracaj kody 401 i 500.

Ekran zdrowia urządzenia: optymalizacja baterii, uprawnienia i połączenie

Co robić, gdy wiadomość nie dochodzi?

Zacznij od statusu z API, bo wskazuje, na którym etapie utknęła wiadomość. Większość problemów to telefon, nie kod.

Status utknął naPrawdopodobna przyczynaCo zrobić
pendingTelefon offline lub uśpiona aplikacjaWłącz internet, wyłącz optymalizację baterii
sent, brak deliveredSieć nie zwraca raportów doręczeniaZwykle brak błędu, wiadomość mogła dojść
failedZły numer, brak środków lub zasięguSprawdź format +48... i kartę SIM
HTTP 400Brak włączonego urządzeniaWłącz telefon w panelu

Co dalej: webhooki, automatyzacje i agenci AI

Gdy wysyłka działa, dodaj odbieranie odpowiedzi klientów. Odpowiedź („TAK” na przypomnienie) trafia na webhook jako MESSAGE_RECEIVED.

Pełne wymagania pól sprawdzisz w dokumentacji API. Aby zacząć, załóż bezpłatne konto i wyślij pierwszy SMS ze swojego telefonu na własny numer.

Najczęściej zadawane pytania

Jak wysłać SMS w PHP?

Użyj cURL wbudowanego w PHP: wyślij POST na /gateway/send-sms z nagłówkami x-api-key i Content-Type: application/json oraz ciałem z json_encode zawierającym recipients i message. Sprawdź kod HTTP przez curl_getinfo, bo 200 oznacza przyjęcie zlecenia.

Jak wysłać SMS w Pythonie za darmo?

Wiadomość wysyłasz biblioteką requests przez API smsportal, a koszt SMS-a to koszt w Twoim abonamencie komórkowym, często zerowy przy nielimitowanych SMS-ach. Dostęp do API ma też bezpłatny plan na start. Nie ma w pełni darmowych bramek, bo za dostarczenie zawsze ktoś płaci.

Czy jest oficjalna biblioteka SMS dla Node.js, PHP lub Pythona?

Nie, smsportal nie ma oficjalnej paczki na npm, Packagist ani PyPI. API to zwykły HTTP z kluczem w nagłówku, więc wystarczą fetch, requests lub cURL. Dwadzieścia linii własnej funkcji zastępuje zewnętrzną zależność.

Jak ponowić wysyłkę SMS po błędzie?

Ponawiaj tylko błędy chwilowe: timeout, błędy sieci i kody 5xx, z rosnącą przerwą. Nie ponawiaj kodów 400 i 401, bo to błąd danych lub klucza, ani 429, który oznacza wyczerpany limit planu. Przed ponowieniem sprawdź listę wiadomości, żeby nie wysłać SMS-a dwa razy.

Jak sprawdzić status SMS-a w kodzie?

Wyślij GET na /gateway/messages z parametrem smsBatchId zwróconym przy wysyłce. Pole status ma wartości pending, dispatched, sent, delivered lub failed. Zamiast odpytywać w pętli, możesz odbierać zdarzenia MESSAGE_DELIVERED i MESSAGE_FAILED webhookiem.

Wyślij pierwszego SMS-a jeszcze dziś

Załóż darmowe konto, podłącz telefon i sprawdź, jak klienci reagują na wiadomości z Twojego numeru.