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 -
fetchjest 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 (
+420lub+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.
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:
| Kodowanie | Znaków w 1. segmencie | Znaków w kolejnych częściach |
|---|---|---|
| GSM-7 (bez znaków diakrytycznych) | 160 | 153 |
| UCS-2 (ze znakami diakrytycznymi) | 70 | 67 |
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:
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.
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.
// .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:
| Status | Kod | Co to znaczy |
|---|---|---|
| 400 | invalid_number | To nie jest prawidłowy czeski ani słowacki numer telefonu. |
| 400 | invalid_body | Treść wiadomości musi mieć od 1 do 765 znaków. |
| 400 | unsupported_country | Numer spoza CZ/SK, SMS-ów tam nie wysyłamy. |
| 400 | recipient_cannot_receive_sms | Numer stacjonarny - SMS by nie dotarł. Nic nie jest naliczane. |
| 402 | insufficient_credit | Saldo 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. |
| 429 | rate_limited | Więcej niż 1 SMS na 2 sekundy albo ponad 100 SMS-ów dziennie na konto. |
| 502 | send_failed | Sieć telefoniczna odrzuciła żądanie. Saldo nie zostało pobrane, należy spróbować ponownie. |
| 502 | send_unknown | Wiadomość 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:
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.
Powiązane artykuły
Pięć pytań przed zakupem recepcjonisty AI
Cena, uruchomienie w 5 minut, przekierowanie numeru w O2/T-Mobile/Vodafone, numery słowackie i VAT - pięć odpowiedzi prosto z cennika i ustawień volai.
Jak zadzwonić do klienta z workflow n8n
Zadzwoń do klienta prosto z n8n: węzeł HTTP Request z REST API volai, dane z workflow w prompcie agenta i wynik rozmowy z powrotem przez webhook.
Jak dać Claude Code numer telefonu przez MCP
Instalacja volai w Claude Code jednym poleceniem, pierwsze połączenie i SMS wysłany zwykłym promptem - bez kodu i bez dokumentacji API.