Strona główna / Artykuły

Wysyłanie i odbieranie SMS-ów w Node.js za pomocą Twilio Webhooks i Express

Stwórz mały serwer Express, który wysyła teksty przez Twilio, odbiera odpowiedzi za pośrednictwem webhooku udostępnionego przez ngrok i automatycznie odpowiada na nie za pomocą TwiML.

Wydawca: REACTAPP.TOP6 min czytania1483 słów

Źródła: **Build a Two-Way SMS with Node.js and Twilio (Full Code Included)**

Większość poradników dotyczących SMS kończy się w momencie, gdy wiadomość opuszcza serwer. Bardziej przydatna jest część dotycząca odbierania wiadomości: zauważanie, że użytkownik odpowiedział, czytanie jego wiadomości i reagowanie na nią. Ten cykl jest podstawą procesów OTP, botów obsługi klienta oraz przypomnień o umowieniach, które potwierdza się jednym słowem. Po ukończeniu tego przewodnika będziesz miał serwer Express, który wysyła wiadomości na telefon i reaguje za każdym razem, gdy telefon odpisze.

Wymagania wstępne

  • Konto Twilio. W chwili pisania tego tekstu darmowy okres próbny obejmuje około 15 dolarów kredytu, co wystarcza na eksperymenty.
  • Postman lub dowolny klient HTTP.
  • Node.js zainstalowany lokalnie.
  • ngrok, który zapewnia Twilio publiczny adres, który przekierowuje ruch na twój laptop.

Minimalny serwer Express

Stwórz folder zawierający plik server.js. Serwer łączy się z localhost na porcie 3000, aktywuje obsługę JSON oraz parsowanie ciała wiadomości w formacie URL-encoded i udostępnia ścieżkę do sprawdzenia stanu pod adresem /api/v1/. Parsowanie w formacie URL-encoded jest istotne: Twilio dostarcza przychodzące wiadomości w formie ciał POST z kodowaniem typu form, więc bez niego req.body byłby pusty później.

const express = require("express");
const bodyParser = require("body-parser");
const hostname = "127.0.0.1";
const port = 3000;
const app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

const baseUrl = "/api/v1/";

app.get(`${baseUrl}`, (req, res) => {
  res.status(200).json({ message: "Hello World" });
});
app.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

Zainstaluj wymagane zależności:

npm init -y
npm install express body-parser

Zrób wywołanie node server.js i otwórz adres http://127.0.0.1:3000/api/v1/; odpowiedź „Hello World” potwierdza poprawne działanie podstaw. Express 4.16+ zawiera już funkcje express.json() i express.urlencoded(), więc użycie body-parser jest opcjonalne w nowych projektach.

Udostępnianie localhost za pomocą ngrok

Twilio działa w publicznej sieci internetowej, a twój serwer na maszynie prywatnej, więc Twilio nie może do niego bezpośrednio dotrzeć. ngrok tworzy tunel i dostarcza publiczną adres URL HTTPS przekierowującą ruch na lokalny port. Heroku kiedyś był darmową opcją do tego celu, ale jego plan darmowy już nie istnieje.

Zainstaluj i zaloguj się do ngrok korzystając z dokumentacji ngrok, a następnie dodaj skrypt do pliku package.json:

"scripts": {
  "ngrok": "ngrok http http://127.0.0.1:3000/"
}

Zachowaj serwer uruchomiony w jednym terminalu, a następnie uruchom tunel w drugim:

npm run ngrok

ngrok wyświetla adres taki jak https://abcd-1234.ngrok-free.app. Zachowaj ten terminal otwarty.

Uwaga: w planie darmowym generowany adres URL zazwyczaj zmienia się za każdym razem, gdy ngrok zostanie ponownie uruchomiony. Gdy działający webhook nagle przestaje reagować, prawdopodobną przyczyną jest przestarzały adres w konsoli Twilio, dlatego należy go aktualizować po każdym restartowaniu.

Konfiguracja Twilio

Na liście kontrolnej konsoli skopiuj swoje SID konta oraz token autoryzacji do pliku .env. Token jest tajemnicą – nigdy go nie dodawaj do repozytorium i umieść plik .env w pliku .gitignore.

ACCOUNT_SID_TWILIO=your_account_sid
AUTH_TOKEN_TWILIO=your_auth_token

Możesz przypiąć produkty w nawigacji bocznej; istotne są tutaj tylko Messaging i Numeri telefonów.

Pobieranie numeru telefonu

  1. Otwórz zakładkę Numeri telefonów, a następnie Kup numer.
  2. Wybierz dowolny dostępny numer; numery w USA zazwyczaj są najtańsze.
  3. Koszt zostanie odjęty z salda próbnego, a numer pojawi się wśród twoich aktywnych numerów.

Tworzenie usługi Messaging

  1. W sekcji Messaging utwórz nową usługę Messaging Service.
  2. W kroku Sender Pool dodaj swój numer.
  • W kroku Integration wybierz Send a webhook i ustaw zarówno Request URL, jak i Fallback URL na swój adres ngrok z dodaną ścieżką /api/v1/sms. Ta ścieżka zostanie stworzona poniżej.
  • Pomij tego opcjonalnego kroku i zakończ proces.
  • Zapisz Messaging Service SID oraz swój numer w pliku .env:

    FROM_TWILIO=+1XXXXXXXXXX
    MESSAGING_SERVICE_SID_TWILIO=your_messaging_service_sid
    

    Podłączanie SDK Twilio do Node

    Zainstaluj SDK wraz z dotenv, który ładuje zawartość pliku .env do zmiennej process.env:

    npm install twilio dotenv
    

    Umieść to na samym początku pliku server.js. Kolejność ma znaczenie: klient odczytuje dane uwierzytelniające podczas swojej inicjalizacji, więc jeśli dotenv zostanie załadowane później, obie wartości będą miały wartość undefined, co spowoduje nieudane uwierzytelnianie przy każdej próbie.

    require("dotenv").config();
    
    const client = require("twilio")(
      process.env.ACCOUNT_SID_TWILIO,
      process.env.AUTH_TOKEN_TWILIO
    );
    

    Wysyłanie wiadomości tekstowej

    Ta ścieżka wywołuje client.messages.create() i raportuje wynik w formacie JSON. Blok try/catch zwraca błędy Twilio, takie jak nieprawidłowy numer, do wywołującego zamiast powodować awarię procesu.

    app.post(`${baseUrl}sendSms`, async (req, res) => {
      try {
        await client.messages.create({
          body: "Hello",
          from: process.env.FROM_TWILIO,
          messagingServiceSid: process.env.MESSAGING_SERVICE_SID_TWILIO,
          to: "+92XXXXXXXXXX", // whoever you're texting
        });
        res.status(200).json({ message: "Text sent successfully" });
      } catch (err) {
        res.status(500).json({ error: err.message });
      }
    });
    

    Znaczenie pól:

    • body: tekst wiadomości.
    • from: twój numer Twilio.
    • messagingServiceSid: łączy wiadomość z usługą oraz jej pulą nadawców.
    • to: odbiorca w formacie E.164 (+1..., +92...).

    Zawołaj POST /api/v1/sendSms z Postman – telefon powinien otrzymać „Hello”. W rzeczywistym kodzie należy odczytać wartości to i body z żądania i je zweryfikować, zamiast używać wartości hardkodowanych.

    Konta testowe mogą wysyłać wiadomości tylko do numerów zweryfikowanych w konsoli Twilio. Jeśli żądanie zostanie zrealizowane, ale nic nie przyjdzie, sprawdź odbiorcę lub dokonaj aktualizacji.

    Odbieranie odpowiedzi przez webhook

    Gdy ktoś wysyła wiadomość na twój numer Twilio, Twilio przesyła ją do webhook konfigurowanego w Messaging Service. Twoj serwer przyjmuje ją i odpowiada za pomocą TwiML, formatu XML używanego przez Twilio do opisania następnych działań. Należy zaimportować ten narzędzie wraz z pozostałymi wymaganiami:

    const MessagingResponse = require("twilio").twiml.MessagingResponse;
    

    Następnie dodaj słuchacz. Klasycznym błędem jest niezgodność ścieżki: trasa musi być identyczna ze ścieżką wpisaną w konsoli, czyli /api/v1/sms. Obsługa loguje req.body.Body i zwraca pusty dokument TwiML w formacie text/xml, co oznacza brak odpowiedzi.

    app.post(`${baseUrl}sms`, (req, res) => {
      // Fires every time someone replies to your number
      const twiml = new MessagingResponse();
    console.log("INCOMING ----->", req.body.Body);
      res.writeHead(200, { "Content-Type": "text/xml" });
      res.end(twiml.toString());
    });
    

    Wpisz swój numer Twilio, a wiadomość pojawi się w terminalu; pętla zostaje zamknięta. Treść wiadomości zawiera również pole From z numerem nadawcy, które jest potrzebne przy śledzeniu rozmów dla poszczególnych użytkowników.

    Ponieważ endpoint jest publiczny, każdy, kto znajdzie adres URL, może wysyłać fałszywe wiadomości. Twilio podpisuje każdy webhook twoim tokenem autoryzacyjnym w nagłówku X-Twilio-Signature, a Node SDK oferuje narzędzia do weryfikacji. Włącz je przed uruchomieniem aplikacji poza środowiskiem lokalnym.

    Odpowiadanie automatycznie

    Serwer słucha, ale pozostaje cichy. Dodanie wiadomości do odpowiedzi w formacie TwiML przekształca ją w bota reagującego na określone słowa kluczowe. Umieść to w obsłudze endpointu /sms po utworzeniu pliku twiml; funkcja ta usuwa zbędne znaki i zamienia tekst na małe litery przed wybraniem odpowiedzi:

    const incoming = req.body.Body.trim().toLowerCase();
    if (incoming === "hi") {
      twiml.message("Hey there! How can I help?");
    } else {
      twiml.message("Hmm, I didn't catch that.");
    }
    

    Twilio wysyła zwróconą odpowiedź do nadawcy, więc nie jest potrzebna dodatkowa wywołanie messages.create().

    Pełny serwer

    Wszystko w jednym pliku, przy czym automatyczna odpowiedź jest wyłączona poprzez komentarze:

    require("dotenv").config();
    
    const express = require("express");
    const bodyParser = require("body-parser");
    const MessagingResponse = require("twilio").twiml.MessagingResponse;
    const client = require("twilio")(
      process.env.ACCOUNT_SID_TWILIO,
      process.env.AUTH_TOKEN_TWILIO
    );
    const hostname = "127.0.0.1";
    const port = 3000;
    const app = express();
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true }));
    
    const baseUrl = "/api/v1/";
    // Health check
    app.get(`${baseUrl}`, (req, res) => {
      res.status(200).json({ message: "Hello World" });
    });
    
    // Goal 1: Send an SMS
    app.post(`${baseUrl}sendSms`, async (req, res) => {
      try {
        await client.messages.create({
          body: "Hello",
          from: process.env.FROM_TWILIO,
          messagingServiceSid: process.env.MESSAGING_SERVICE_SID_TWILIO,
          to: "+92XXXXXXXXXX", // whoever you're texting
        });
        res.status(200).json({ message: "Text sent successfully" });
      } catch (err) {
        res.status(500).json({ error: err.message });
      }
    });
    
    // Goal 2: Receive replies (this endpoint must match your Twilio webhook)
    app.post(`${baseUrl}sms`, (req, res) => {
      const twiml = new MessagingResponse();
      console.log("INCOMING ----->", req.body.Body);
      // Optional auto-reply:
      // const incoming = req.body.Body.trim().toLowerCase();
      // if (incoming === "hi") {
      //   twiml.message("Hey there! How can I help?");
      // } else {
      //   twiml.message("Hmm, I didn't catch that.");
      // }
      res.writeHead(200, { "Content-Type": "text/xml" });
      res.end(twiml.toString());
    });
    
    app.listen(port, hostname, () => {
      console.log(`Server running at http://${hostname}:${port}/`);
    });
    

    Główne wnioski

    • Wysyłanie SMS-ów to jedno wywołanie SDK; odbieranie SMS-ów odbywa się za pomocą webhooka, więc serwer musi być dostępny i rozumieć język TwiML.
    • Twilio wysyła dane w formacie kodowanym, stąd konieczność użycia parsera do dekodowania URL.
    • Ścieżka webhooka w konsoli i trasa Express muszą się zgadzać, a ponowne uruchomienie darmowego tunelu oznacza nową ścieżkę URL.
    • Nieudane testy często wynikają z ograniczeń dotyczących numerów do weryfikacji, a nie z błędów.
    • Niechciane błędy mogą wynikać z nieprawidłowych podpisów lub wartości hardkodowanych – należy je sprawdzić przed faktycznym użyciem.

    Stąd możesz przechowywać rozmowy, rozwijać bota do analizy słów kluczowych lub dodać logowanie przez OTP. Aby dowiedzieć się więcej na temat wzorców obsługi powtórek i wywołań zwrotnych, zapoznaj się z sześcioma wzorcami integracji umożliwiającymi niezawodne łączenie usług Node.js.