Главная / Статьи

Отправка и прием SMS в Node.js с использованием Twilio Webhooks и Express

Создайте небольшой сервер Express, который отправляет тексты через Twilio, принимает ответы по вебхуку, доступному через ngrok, и автоматически отвечает на них с помощью TwiML.

Издатель: REACTAPP.TOP6 мин чтения1483 слов

Источники: **Build a Two-Way SMS with Node.js and Twilio (Full Code Included)**

Большинство учебников по SMS останавливаются на момент отправки сообщения с вашего сервера. Гораздо более полезной является вторая часть — обработка входящих сообщений: отслеживание ответов пользователя, чтение текста и формирование ответа. Именно такой цикл обеспечивает работу процессов генерации одноразовых паролей, ботов поддержки и напоминаний о встречах, которые пользователи подтверждают одним словом. К концу этого руководства у вас будет сервер Express, который отправляет сообщения на телефон и реагирует каждый раз, когда телефон отвечает.

Предпосылки

  • Учетная запись в Twilio. На момент написания статьи бесплатная пробная версия включает примерно 15 долларов кредита, что более чем достаточно для экспериментов.
  • Postman или любой другой HTTP-клиент.
  • Node.js, установленный локально.
  • ngrok, который предоставляет Twilio публичный адрес, перенаправляющий запросы на ваш ноутбук.

Минимальный сервер Express

Создайте папку с файлом server.js. Сервер подключается к localhost по порту 3000, включает обработку JSON и декодирование тела запроса в URL-формате, а также предоставляет маршрут для проверки работоспособности по адресу /api/v1/. Декодирование URL-формата важно: Twilio передает входящие сообщения в формате POST с кодировкой формы, поэтому без него значение req.body позже будет пустым.

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}/`);
});

Установите необходимые зависимости:

npm init -y
npm install express body-parser

Запустите команду node server.js и откройте адрес http://127.0.0.1:3000/api/v1/; ответ «Hello World» подтверждает правильную работу основных функций. В версиях Express 4.16+ уже включены функции express.json() и express.urlencoded(), поэтому использование библиотеки body-parser в новых проектах является необязательным.

Обнаружение localhost с помощью ngrok

Twilio работает в открытом Интернете, а ваш сервер — на частном устройстве, поэтому Twilio не может к нему напрямую подключиться. ngrok создаёт туннель и предоставляет вам публичный HTTPS-адрес, перенаправляющий запросы на локальный порт. Раньше Heroku был бесплатным вариантом для этого, но его бесплатный тариф больше не существует.

Установите и авторизуйтесь в ngrok с помощью документации ngrok, затем добавьте скрипт в файл package.json:

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

Запустите сервер в одной терминале, а затем запустите туннель в другой:

npm run ngrok

ngrok выводит адрес вида https://abcd-1234.ngrok-free.app. Оставьте этот терминал открытым.

Будьте осторожны: при использовании бесплатного тарифа сгенерированный URL обычно меняется каждый раз при перезапуске ngrok. Если рабочий webhook внезапно перестаёт работать, вероятной причиной является устаревший URL в консоли Twilio, поэтому обновляйте его после каждого перезапуска.

Настройка Twilio

В панели консоли скопируйте SID вашего аккаунта и токен аутентификации в файл .env. Этот токен является конфиденциальной информацией: никогда не добавляйте его в репозиторий, а также включите файл .env в список .gitignore.

ACCOUNT_SID_TWILIO=your_account_sid
AUTH_TOKEN_TWILIO=your_auth_token

Вы можете фиксировать продукты в боковой навигации; здесь имеют значение только Мессенджеры и Номера телефонов.

Получение номера телефона

  1. Откройте раздел Номера телефонов, затем выберите опцию Купить номер.
  2. Выберите любой доступный номер; номера в США обычно стоят дешевле.
  3. Стоимость будет вычтена из пробного баланса, и номер появится среди ваших активных номеров.

Создание сервиса мессенджеров

  1. В разделе Мессенджеры создайте новый Сервис мессенджеров.
  2. На этапе Пул отправителей добавьте свой номер.
  • На этапе Интеграция выберите опцию Отправить webhook и укажите как URL запроса, так и URL резерва как ваш адрес ngrok с добавлением пути /api/v1/sms. Этот маршрут описан ниже.
  • Пропустите необязательный этап и завершите процесс.
  • Сохраните Messaging Service SID и ваш номер в файле .env:

    FROM_TWILIO=+1XXXXXXXXXX
    MESSAGING_SERVICE_SID_TWILIO=your_messaging_service_sid
    

    Подключение SDK Twilio к Node

    Установите SDK вместе с библиотекой dotenv, которая загружает содержимое файла .env в переменные среды process.env:

    npm install twilio dotenv
    

    Разместите этот код в самом верхнем разделе файла server.js. Порядок имеет значение: клиент считывает учетные данные при создании, поэтому если dotenv будет загружена позже, оба значения станут undefined, и каждая попытка отправки сообщения завершится ошибкой аутентификации.

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

    Отправка текстового сообщения

    Этот маршрут вызывает client.messages.create() и возвращает результат в формате JSON. Блоки try/catch передают ошибки Twilio, такие как некорректный номер, вызывающему коду вместо того, чтобы привести к сбою процесса.

    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 });
      }
    });
    

    Значение полей:

    • body: текст сообщения.
    • from: ваш номер в Twilio.
    • messagingServiceSid: связывает сообщение с сервисом и пулом отправителей.
    • to: получатель в формате E.164 (+1..., +92...).

    Вызовите POST /api/v1/sendSms из Postman — телефон должен получить сообщение «Hello». В реальном коде необходимо читать значения to и body из запроса и проверять их, а не использовать жестко заданные значения.

    Пилотные учётные записи могут отправлять сообщения только на номера, проверенные в консоли Twilio. Если запрос успешен, но сообщение не приходит, проверьте получателя или обновите пакет.

    Получение ответов через webhook

    Когда кто-то отправляет сообщение на ваш номер в Twilio, сервис Twilio отправляет его на webhook, настроенный в Messaging Service. Ваш сервер принимает сообщение и отвечает с использованием TwiML — формата XML от Twilio для указания следующих действий. Импортируйте необходимые вспомогательные компоненты вместе с остальными файлами:

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

    Затем добавьте обработчик. Классическая ошибка — несоответствие пути: маршрут должен совпадать с путём, указанным в консоли, а именно /api/v1/sms. Обработчик записывает содержимое req.body.Body и возвращает пустой документ TwiML в формате text/xml, что означает отсутствие ответа.

    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());
    });
    

    Введите номер Twilio, и сообщение появится в терминале; цикл завершается. В заголовке также указывается From — номер отправителя, который понадобится при отслеживании разговоров по пользователям.

    Поскольку конечная точка является публичной, любой, кто найдет URL, может отправлять фальшивые сообщения. Twilio подписывает каждый webhook с помощью вашего Auth Token в заголовке X-Twilio-Signature, а Node SDK предоставляет инструменты для проверки. Включите их перед развертыванием за пределами локальных тестов.

    Автоматический ответ

    Сервер прослушивает сообщения, но не реагирует. Добавление сообщения в ответ в формате TwiML превращает его в бота на основе ключевых слов. Разместите этот код в обработчике /sms после создания объекта twiml; он обрезает и приводит текст к маленькой букве перед выбором ответа:

    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 отправляет возвращенный ответ отправителю, поэтому не требуется дополнительный вызов messages.create().

    Полностью готовый сервер

    Всё в одном файле, причем автоматический ответ оставлен комментированным:

    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}/`);
    });
    

    Основные выводы

    • Отправка SMS — это один вызов SDK; прием SMS осуществляется через webhook, поэтому сервер должен быть доступен и понимать формат TwiML.
    • Twilio отправляет данные в формате формы, поэтому используется парсер с кодировкой URL.
    • Путь webhook в консоли и маршрут Express должны совпадать, а перезапуск бесплатного туннеля приводит к изменению URL.
    • Незаметные сбои тестов часто связаны с ограничениями по проверке номеров, а не с багами.
    • Перед реальным использованием необходимо проверять подписи и удалять жестко заданные значения.

    Отсюда вы можете сохранять разговоры, совершенствовать бота на основе ключевых слов или добавлять вход через OTP. Чтобы узнать больше о паттернах обработки повторных запросов и автоматической перепробовки, ознакомьтесь с шестью паттернами интеграции для надежного подключения сервисов Node.js.