Przejdź do treści

Wysyłanie SMS przez API w 10 minut w Node.js

September 2, 2026 · 6 minut czytania · Zaktualizowano October 9, 2026

Aby wysłać SMS z Node.js, wystarczy jedno żądanie POST na https://volai.cz/v1/messages z nagłówkiem Authorization: Bearer vk_... i treścią JSON {to, body}. Node 20 ma fetch wbudowany, więc nie potrzeba żadnego dodatkowego pakietu. Działający przykład znajduje się zaraz pod tym akapitem, razem z obsługą błędów i idempotencją, aby awaria sieci nie wysłała tej samej wiadomości dwa razy.

Czego będą Państwo potrzebować

  • Node.js 18 lub nowszy - fetch jest we wbudowanej bibliotece już od tej wersji; zalecamy aktualnie wspieraną linię LTS (24, stan na 2 września 2026).
  • Konto w volai i klucz API z portalu (Ustawienia -> API i MCP), w postaci vk_....
  • Co najmniej jedno doładowanie salda. SMS-y działają dopiero po pierwszym doładowaniu, saldo powitalne nie pokrywa ich wysyłki - można za nie wypróbować rozmowy, ale nie wiadomości tekstowe.
  • Numer telefonu odbiorcy w Czechach lub na Słowacji (+420 lub +421) - do innych krajów SMS-ów nie wysyłamy.
  • Własny numer telefonu NIE jest potrzebny. Wiadomości wychodzą pod wspólną nazwą SMSinfo - operatorzy w sieciach czeskich dopuszczają tylko zatwierdzone nazwy, więc przy wspólnym nadawcy nie można ustawić własnej nazwy nadawcy, a na SMSinfo odbiorca nie może odpowiedzieć. Swoją tożsamość należy więc zapisać w samej treści wiadomości, jak w przykładzie poniżej. Jeśli ma być możliwa odpowiedź, należy wysłać wiadomość z własnego numeru SMS (dodatek, patrz uwaga niżej).

Wysyłka pierwszego SMS-a

Żądanie potrzebuje tylko dwóch pól: to (numer odbiorcy) i body (treść wiadomości). Odpowiedź zwraca id wiadomości, status, cenę w halerzach (priceHal) oraz liczbę segmentów (segments) - do obu za chwilę wrócimy.

javascript
const response = await fetch("https://volai.cz/v1/messages", {
  method: "POST",
  headers: {
    Authorization: "Bearer vk_TWOJ_KLUCZ",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    to: "+420777123456",
    body: "Cześć z volai! - Moja aplikacja",
  }),
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error("volai " + error.code + ": " + error.message);
}

const message = await response.json();
console.log(message);
// { id: "msg_7c1f9a2e", status: "sent", priceHal: 136, segments: 1 }

Ile kosztuje SMS i czym są segmenty?

Jeden SMS w jednym segmencie kosztuje 1,36 kr bez VAT (cena obowiązująca na 2 września 2026). To, ile segmentów zajmie wiadomość, zależy od tego, czy zawiera znaki diakrytyczne - operatorzy liczą SMS-y według standardu GSM 03.38:

KodowanieZnaków w 1. segmencieZnaków w kolejnych częściach
GSM-7 (bez znaków diakrytycznych)160153
UCS-2 (ze znakami diakrytycznymi)7067

Wystarczy JEDEN znak spoza podstawowego alfabetu GSM-7 - typowo polski lub czeski znak diakrytyczny (ą ć ę ł ń ó ś ź ż, ě š č ř ž ý á í ů) - a cała wiadomość przełącza się na UCS-2, nawet jeśli w tekście jest tylko jedna taka litera. Wskazówka: aby mieć pewność, że wiadomość zmieści się w jednym segmencie, należy pisać bez znaków diakrytycznych.

Przykład: wiadomość ze znakami diakrytycznymi powyżej 70 znaków

Ta wiadomość ma 120 znaków i zawiera znaki diakrytyczne, więc jest liczona jako UCS-2 (limit 67 znaków na segment w wiadomości wieloczęściowej) - wychodzą 2 segmenty, czyli 2,72 kr, a nie 1,36 kr:

javascript
await fetch("https://volai.cz/v1/messages", {
  method: "POST",
  headers: {
    Authorization: "Bearer vk_TWOJ_KLUCZ",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    to: "+420777123456",
    body: "Przypominamy o jutrzejszej wizycie o 10:00 - jeśli termin Państwu nie odpowiada, prosimy o odpowiedź, a znajdziemy inny.",
  }),
});
// -> { id: "msg_9a3f1c7d", status: "sent", priceHal: 272, segments: 2 }

Idempotencja: bezpieczne ponawianie żądania

Warto dodać nagłówek Idempotency-Key z dowolnym własnym ciągiem znaków, na przykład identyfikatorem zamówienia. Jeśli żądanie zakończy się błędem z powodu awarii sieci, a workflow ponowi je z TYM SAMYM nagłówkiem, volai zwróci zapisaną odpowiedź z pierwszej próby i nie wyśle wiadomości po raz drugi - klucz obowiązuje 24 godziny.

bash
curl -X POST https://volai.cz/v1/messages \
  -H "Authorization: Bearer vk_TWOJ_KLUCZ" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: zamowienie-4471" \
  -d '{"to": "+420777123456", "body": "Państwa zamówienie nr 4471 zostało przyjęte."}'

Bezpieczne obchodzenie się z kluczem API

Klucz API (vk_...) ma taką samą wagę jak hasło - każdy, kto go zna, może w Państwa imieniu wysyłać wiadomości i wydawać saldo z Państwa konta. Dlatego nigdy nie należy wpisywać go do kodu źródłowego, który trafia do repozytorium. Jego miejsce jest w zmiennej środowiskowej (process.env.VOLAI_API_KEY w Node.js) lub w menedżerze sekretów używanym przez hosting.

javascript
// .env.local (nigdy nie commituj do repozytorium)
// VOLAI_API_KEY=vk_twoj_klucz

const response = await fetch("https://volai.cz/v1/messages", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + process.env.VOLAI_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ to, body }),
});

Endpoint należy wywoływać zawsze z serwera, nie z przeglądarki - każdy mógłby odczytać klucz w narzędziach deweloperskich przeglądarki i zacząć wysyłać własne wiadomości z Państwa konta. Jeśli klucz wycieknie (na przykład przez przypadkowy commit), należy wygenerować w portalu nowy, a stary unieważnić - stara wartość przestaje działać natychmiast.

Najpierw bezpieczny test

Zanim wysyłka SMS-ów zostanie podłączona do prawdziwego procesu (na przykład potwierdzenia zamówienia), pierwsze wiadomości warto wysłać na własny numer telefonu. Zobaczą Państwo dokładnie to, co zobaczy klient - jak wygląda nadawca SMSinfo, jak długo trwa doręczenie i czy znaki diakrytyczne są liczone tak, jak Państwo oczekują. Dopiero potem należy podmienić to na prawdziwy numer klienta.

Co robić, gdy API zwróci błąd?

Odpowiedź z błędem ma zawsze postać {error: {code, message}} i status HTTP zależny od rodzaju błędu:

StatusKodCo to znaczy
400invalid_numberTo nie jest prawidłowy czeski ani słowacki numer telefonu.
400invalid_bodyTreść wiadomości musi mieć od 1 do 765 znaków.
400unsupported_countryNumer spoza CZ/SK, SMS-ów tam nie wysyłamy.
400recipient_cannot_receive_smsNumer stacjonarny - SMS by nie dotarł. Nic nie jest naliczane.
402insufficient_creditSaldo nie pokrywa ceny wszystkich segmentów wiadomości albo konto nie zostało jeszcze doładowane - SMS-y działają dopiero po pierwszym doładowaniu, saldo powitalne nie pokrywa ich wysyłki.
429rate_limitedWięcej niż 1 SMS na 2 sekundy albo ponad 100 SMS-ów dziennie na konto.
502send_failedSieć telefoniczna odrzuciła żądanie. Saldo nie zostało pobrane, należy spróbować ponownie.
502send_unknownWiadomość przekazaliśmy do sieci, ale potwierdzenie nie dotarło - jest naliczana, prosimy nie wysyłać jej ponownie na ślepo.

Jakie stany może mieć wiadomość?

Pole status w wykazie (GET /v1/messages) i w szczegółach pojedynczej wiadomości przyjmuje pięć wartości: pending (zapisana, jeszcze się wysyła), sent (operator potwierdził wysłanie), failed (operator odrzucił - nie jest naliczana), unknown (potwierdzenie doręczenia nie dotarło, zwykle przez przekroczenie czasu po stronie operatora - mimo to jest naliczana w pełnej cenie, bo wiadomość mogła dojść) oraz received (tylko przy wiadomościach przychodzących, czyli z direction=in; wysłane wiadomości go nie mają).

Każda wiadomość niesie także pole direction (out przy wysłanych, in przy odebranych), smsNumber (numer, z którego wiadomość wyszła lub na który przyszła; przy wiadomościach pod wspólną nazwą SMSinfo jest to null) oraz deliveryStatus (delivered lub undelivered, potwierdzenie doręczenia od operatora; powstaje tylko przy SMS-ach wysłanych z numeru SMS, przy pozostałych wiadomościach jest null).

Jak wysłać wiele SMS-ów naraz?

Limit dopuszcza najwyżej jeden SMS na dwie sekundy na konto i najwyżej 100 SMS-ów dziennie. Przy wysyłce wielu wiadomości naraz (na przykład przypomnień o spotkaniach) prościej jest krótko poczekać między żądaniami, niż łapać rate_limited i próbować ponownie:

javascript
const message = "Przypominamy o jutrzejszym terminie - Moja aplikacja";

for (const to of recipients) {
  const response = await fetch("https://volai.cz/v1/messages", {
    method: "POST",
    headers: {
      Authorization: "Bearer vk_TWOJ_KLUCZ",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ to, body: message }),
  });

  if (!response.ok) {
    const { error } = await response.json();
    console.error("volai " + to + ": " + error.code);
  }

  // Limit to najwyżej 1 SMS na 2 sekundy na konto - krótka przerwa między
  // żądaniami jest prostsza niż obsługa rate_limited i ponawianie.
  await new Promise((resolve) => setTimeout(resolve, 2100));
}

SMS-y odbierają dwa rodzaje numerów

Numery głosowe volai nie odbierają SMS-ów, więc odpowiedź na wiadomość wysłaną pod wspólną nazwą SMSinfo nigdzie się nie pojawi. Aby odbierać SMS-y i wysyłać je z własnego numeru, należy wybrać dodatek numer komórkowy: czeski numer tylko do SMS-ów, bez połączeń, 290 kr na 30 dni bez VAT. Albo zamówić numer: numer do połączeń i SMS - praski numer stacjonarny, na którym odbiera się połączenia i SMS-y, 49 kr na 30 dni bez VAT (opłata jest pobierana dopiero po aktywacji, która trwa zwykle do 1 dnia roboczego). W obu przypadkach odebrane SMS-y są wliczone w cenę. Odebrane wiadomości można odczytać w GET /v1/messages?direction=in, w narzędziu MCP list_messages lub w webhooku message.received, a przechowujemy je 90 dni; wysyłka z numeru SMS kosztuje 1,90 kr za segment, z numeru: numer do połączeń i SMS 2,50 kr za segment, i jest możliwa tylko na numery czeskie. SMS z numeru wysyła się polem fromNumber w POST /v1/messages (numer należy wziąć z GET /v1/sms-numbers lub z GET /v1/voice-sms-numbers). Dzienny limit wysyłki z numeru to 20 SMS-ów na konto w pierwszych 30 dniach od zakupu numeru, potem 50. Na koncie mogą być najwyżej dwa numery SMS i można je kupić dopiero po pierwszym doładowaniu salda; tak samo jest z numerami: numer do połączeń i SMS - najwyżej dwa na konto, a zamówić je można dopiero po pierwszym doładowaniu. Kodów weryfikacyjnych od usług (banki, Google i podobne) nie gwarantujemy, numery służą do pisania z ludźmi, a odbiór sprawdziliśmy w sieci O2. Szczegóły są w sekcjach Numery SMS i Numery: numer do połączeń i SMS w dokumentacji API. Kiedy do odbioru SMS-ów lepszym wyborem jest Twilio, opisuje porównanie Twilio i volai.

Podsumowanie: jedno POST /v1/messages z polami to i body wysyła SMS, nagłówek Idempotency-Key chroni workflow przed podwójną wysyłką przy ponowieniu żądania, a cena liczona jest za segmenty, zależnie od tego, czy tekst zawiera znaki diakrytyczne. Klucz należy trzymać poza repozytorium, a pierwszą wiadomość wysłać do siebie.

Co dalej

Pełna dokumentacja REST API (numery, połączenia, webhooki, agenci) jest pod adresem /docs/api. Chcą Państwo wiedzieć od razu, gdy volai przekaże wiadomość operatorowi, bez odpytywania GET /v1/messages? Można zasubskrybować zdarzenie message.sent - potwierdza ono tylko przyjęcie przez operatora, a nie doręczenie do telefonu (przy wiadomościach pod wspólną nazwą volai tego nie widzi, patrz wyżej; wiadomości wysłane z własnego numeru SMS mają potwierdzenie doręczenia) - patrz dokumentacja webhooków. A jeśli oprócz SMS-ów potrzebny jest także numer telefonu, który sam odbiera połączenia, warto zajrzeć do strony Recepcjonista AI.

Wypróbuj volai - pierwsze 50 kr na nasz koszt

Konto zakładają Państwo w minutę i od razu mają na nim saldo startowe na połączenia i numer - SMS-y wyślą Państwo po pierwszym doładowaniu.

Wypróbować za darmo