Send SMS fra Node.js på 10 minutter
September 2, 2026 · 6 minutter læsetid · Opdateret October 9, 2026
For at sende en SMS fra Node.js er det nok med én POST-forespørgsel til https://volai.cz/v1/messages med headeren Authorization: Bearer vk_... og en JSON-body med {to, body}. Node 20 har fetch indbygget, så der skal ikke installeres nogen pakke. Et fungerende eksempel følger lige nedenfor, med fejlhåndtering og idempotens, så et netværkshak ikke sender den samme besked to gange.
Det skal der bruges
- Node.js 18 eller nyere -
fetcher indbygget siden denne version; vi anbefaler den aktuelt understøttede LTS-linje (24, pr. 2. september 2026). - En volai-konto og en API-nøgle fra portalen (Indstillinger -> API & MCP) i formen
vk_.... - Mindst én optankning af saldoen. SMS kræver en første optankning; velkomstsaldoen dækker dem ikke - den kan bruges til at prøve opkald, ikke SMS.
- Et modtagernummer i Tjekkiet eller Slovakiet (
+420eller+421) - vi sender ikke SMS til andre lande. - Der skal IKKE bruges et eget telefonnummer. Beskederne sendes under det fælles afsendernavn
SMSinfo- operatørerne tillader kun godkendte afsendernavne på tjekkiske net, så et eget afsendernavn er ikke muligt med den fælles afsender, og modtagere kan ikke svare til SMSinfo. Afsenderens identitet skrives derfor i selve beskedteksten, som i eksemplet nedenfor. Hvis der skal kunne svares, sendes i stedet fra et eget mobilnummer (et tilkøb, se bemærkningen nedenfor).
Send den første SMS
Forespørgslen kræver kun to felter: to (modtagernummer) og body (beskedtekst). Svaret indeholder beskedens id, dens status, prisen i hundrededele af en krone (priceHal) og antallet af segmenter (segments) - mere om begge dele nedenfor.
const response = await fetch("https://volai.cz/v1/messages", {
method: "POST",
headers: {
Authorization: "Bearer vk_DIN_NOGLE",
"Content-Type": "application/json",
},
body: JSON.stringify({
to: "+420777123456",
body: "Hej fra volai! - Min App",
}),
});
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 }Hvad koster en SMS, og hvad er segmenter?
En SMS med ét segment koster 1,36 kr uden moms (pris pr. 2. september 2026). Hvor mange segmenter en besked fylder, afhænger af, om den indeholder tegn uden for GSM-7-alfabetet - operatørerne tæller SMS efter standarden GSM 03.38:
| Kodning | Tegn i 1. segment | Tegn i de følgende dele |
|---|---|---|
| GSM-7 (standardtegn, også æ ø å) | 160 | 153 |
| UCS-2 (tegn uden for GSM-7) | 70 | 67 |
Ét enkelt tegn uden for det grundlæggende GSM-7-alfabet (typografiske anførselstegn “ ”, emoji, de fleste ikke-latinske skrifttegn, tjekkiske tegn som ě š č ř ž) skifter HELE beskeden til UCS-2, selv om det er det eneste af slagsen i teksten. Æ, ø og å hører derimod til GSM-7. Tip: for pålideligt at holde sig inden for ét segment bruges kun almindelige GSM-7-tegn og lige anførselstegn.
Eksempel: en besked med specialtegn over 70 tegn
Denne besked er 121 tegn lang og indeholder typografiske anførselstegn, så den tælles som UCS-2 (67 tegn pr. segment ved en flerdelt besked) - det giver 2 segmenter, altså 2,72 kr i stedet for 1,36 kr:
await fetch("https://volai.cz/v1/messages", {
method: "POST",
headers: {
Authorization: "Bearer vk_DIN_NOGLE",
"Content-Type": "application/json",
},
body: JSON.stringify({
to: "+420777123456",
body: "Hej, vi minder om aftalen i morgen kl. 10:00. Passer tidspunktet ikke, så skriv blot “ny tid”, så finder vi en anden tid.",
}),
});
// -> { id: "msg_9a3f1c7d", status: "sent", priceHal: 272, segments: 2 }Idempotens: gentag forespørgsler sikkert
Tilføj en Idempotency-Key-header med en vilkårlig selvvalgt streng, for eksempel et ordre-id. Hvis forespørgslen fejler på grund af en netværksfejl, og arbejdsgangen gentager den med SAMME header, returnerer volai det gemte svar fra det første forsøg i stedet for at sende beskeden en gang til - nøglen gælder i 24 timer.
curl -X POST https://volai.cz/v1/messages \
-H "Authorization: Bearer vk_DIN_NOGLE" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ordre-4471" \
-d '{"to": "+420777123456", "body": "Ordre nr. 4471 er modtaget."}'Opbevar API-nøglen sikkert
En API-nøgle (vk_...) vejer lige så tungt som en adgangskode - den, der har den, kan sende beskeder og bruge kontoens saldo i kontoindehaverens navn. Skriv den derfor aldrig ind i kildekode, der committes til et repository. Den hører hjemme i en miljøvariabel (process.env.VOLAI_API_KEY i Node.js) eller i hostingudbyderens secrets-manager.
// .env.local (må aldrig committes til repositoriet)
// VOLAI_API_KEY=vk_din_nogle
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 }),
});Endpointet kaldes fra en server, aldrig fra browseren - enhver kan læse nøglen i browserens udviklerværktøjer og begynde at sende beskeder fra kontoen. Hvis en nøgle nogensinde lækker (for eksempel ved et uheldigt commit), oprettes en ny i portalen, og den gamle tilbagekaldes - den gamle værdi holder op med at virke med det samme.
Test først på en sikker måde
Før SMS-afsendelsen kobles på en rigtig proces (for eksempel ordrebekræftelser), sendes de første par beskeder til et eget telefonnummer. Så ses præcis det, kunden ser - hvordan afsendernavnet SMSinfo tager sig ud, hvor lang tid leveringen tager, og om segmenteringen opfører sig som forventet. Først derefter skiftes to til en rigtig kundes nummer.
Hvad gør man, når API'et returnerer en fejl?
Et fejlsvar har altid formen {error: {code, message}}, med en HTTP-status, der svarer til fejlens art:
| Status | Kode | Hvad det betyder |
|---|---|---|
| 400 | invalid_number | Ikke et gyldigt tjekkisk eller slovakisk telefonnummer. |
| 400 | invalid_body | Beskedteksten skal være på 1 til 765 tegn. |
| 400 | unsupported_country | Nummer uden for CZ/SK - vi sender ikke SMS dertil. |
| 400 | recipient_cannot_receive_sms | Et fastnetnummer - SMS'en kunne ikke nå frem. Faktureres ikke. |
| 402 | insufficient_credit | Saldoen dækker ikke prisen for alle segmenter, eller kontoen er ikke fyldt op endnu - SMS kræver en første optankning, velkomstsaldoen dækker dem ikke. |
| 429 | rate_limited | Mere end 1 SMS pr. 2 sekunder eller over 100 SMS pr. konto pr. dag. |
| 502 | send_failed | Operatøren afviste forespørgslen. Saldoen blev ikke belastet, forsøg igen. |
| 502 | send_unknown | Vi afleverede beskeden til operatøren, men fik ingen leveringsbekræftelse - den faktureres, så send den ikke blindt igen. |
Hvilke tilstande kan en besked have?
Feltet status, både i listen (GET /v1/messages) og på en enkelt besked, har fem mulige værdier: pending (gemt, sendes stadig), sent (operatøren bekræftede afsendelsen), failed (operatøren afviste den - faktureres ikke), unknown (der kom ingen leveringsbekræftelse, typisk en timeout hos operatøren - den faktureres stadig fuldt ud, fordi beskeden kan være nået frem) og received (kun for indgående beskeder, det vil sige med direction=in; afsendte beskeder har den aldrig).
Hver besked har også felterne direction (out for afsendte beskeder, in for modtagne), smsNumber (det nummer, beskeden blev sendt fra eller modtaget på; null for beskeder under det fælles afsendernavn SMSinfo) og deliveryStatus (delivered eller undelivered, en leveringsrapport fra operatøren; den findes kun for beskeder sendt fra et mobilnummer og er null for alle andre).
Hvordan sender man en række SMS?
Hastighedsgrænsen tillader højst én SMS hvert andet sekund pr. konto, op til 100 SMS om dagen. Når der sendes flere beskeder ad gangen (for eksempel aftalepåmindelser), er en kort pause mellem forespørgslerne enklere end at fange rate_limited og forsøge igen:
const message = "Påmindelse: aftalen er i morgen - Min App";
for (const to of recipients) {
const response = await fetch("https://volai.cz/v1/messages", {
method: "POST",
headers: {
Authorization: "Bearer vk_DIN_NOGLE",
"Content-Type": "application/json",
},
body: JSON.stringify({ to, body: message }),
});
if (!response.ok) {
const { error } = await response.json();
console.error("volai " + to + ": " + error.code);
}
// Grænsen er højst 1 SMS pr. 2 sekunder pr. konto - en kort pause
// mellem kaldene er enklere end at fange rate_limited og forsøge igen.
await new Promise((resolve) => setTimeout(resolve, 2100));
}To slags numre modtager SMS
volais stemmenumre kan ikke modtage SMS, så et svar på en besked sendt under det fælles navn SMSinfo dukker ikke op nogen steder. For at modtage SMS og sende fra et eget nummer vælges tilkøbet mobilnummer (et tjekkisk nummer kun til SMS, uden opkald, 290 kr pr. 30 dage uden moms) eller et nummer til opkald og SMS (et fastnetnummer i Prag, 49 kr pr. 30 dage uden moms, som først trækkes efter aktivering). Modtagne SMS er inkluderet i begge. Modtagne beskeder læses via GET /v1/messages?direction=in, via MCP-værktøjet list_messages eller via webhooken message.received, og vi opbevarer dem i 90 dage; afsendelse fra et mobilnummer koster 1,90 kr pr. segment, fra et nummer til opkald og SMS 2,50 kr, og virker kun til tjekkiske numre. For at sende fra et nummer tilføjes feltet fromNumber til POST /v1/messages (nummeret hentes fra GET /v1/sms-numbers eller GET /v1/voice-sms-numbers). Den daglige grænse for afsendelse fra et nummer er 20 SMS pr. konto i de første 30 dage efter købet, derefter 50. En konto kan højst have to mobilnumre og kan først købe et efter den første optankning; det samme gælder numre til opkald og SMS. Vi garanterer ikke bekræftelseskoder fra tjenester (banker, Google og lignende) - numrene er beregnet til at skrive med mennesker, og modtagelsen er blevet verificeret i O2-nettet. Detaljerne står i afsnittene Mobilnumre og Numre til opkald og SMS i API-dokumentationen. Hvornår Twilio er det bedre valg til at modtage SMS, beskriver sammenligningen af volai og Twilio.
Kort sagt: ét kald til POST /v1/messages med to og body sender SMS'en, en Idempotency-Key-header beskytter en arbejdsgang, der forsøger igen, mod at sende den to gange, og prisen er pr. segment, afhængigt af om teksten indeholder tegn uden for GSM-7. Hold nøglen ude af repositoriet, og send den første besked til sig selv, før der sendes til andre.
Hvad er det næste
Den fulde REST API-reference (numre, opkald, webhooks, agenter) findes på /docs/api. Skal det være muligt at vide, i det øjeblik volai afleverer en besked til operatøren, uden at polle GET /v1/messages? Abonnér på hændelsen message.sent - den bekræfter kun, at operatøren har modtaget beskeden, ikke at den er nået frem til telefonen (det kan volai ikke se for beskeder under det fælles navn, se ovenfor; beskeder sendt fra et eget mobilnummer får en leveringsrapport) - se dokumentationen til webhooks. Og er der brug for et telefonnummer, der også tager imod opkald og ikke kun SMS, er AI receptionist et kig værd.
Prøv volai - de første 50 kr er på os
Du opretter en konto på et minut og får straks en startsaldo til opkald og nummer - SMS kan sendes efter den første opfyldning.
Læs videre
Relaterede artikler
Fem spørgsmål, før man anskaffer en AI receptionist
Pris, drift på 5 minutter, viderestilling hos O2/T-Mobile/Vodafone, slovakiske numre og moms - fem svar direkte fra prislisten og indstillingerne.
Sådan ringes der til en kunde fra et n8n-workflow
Ring til en kunde direkte fra n8n: en HTTP Request-node mod volais REST API, variabler fra workflowet i agentens prompt og opkaldsresultatet tilbage via webhook.
Giv Claude Code et telefonnummer via MCP
Installer volai i Claude Code med én kommando, giv det et telefonnummer, og lad det ringe og sende SMS blot med en prompt - uden kode og uden API-dokumentation.