Отправка и прием SMS в Node.js с использованием Twilio Webhooks и Express
Создайте небольшой сервер Express, который отправляет тексты через Twilio, принимает ответы по вебхуку, доступному через ngrok, и автоматически отвечает на них с помощью TwiML.
Источники: **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
Вы можете фиксировать продукты в боковой навигации; здесь имеют значение только Мессенджеры и Номера телефонов.
Получение номера телефона
- Откройте раздел Номера телефонов, затем выберите опцию Купить номер.
- Выберите любой доступный номер; номера в США обычно стоят дешевле.
- Стоимость будет вычтена из пробного баланса, и номер появится среди ваших активных номеров.
Создание сервиса мессенджеров
- В разделе Мессенджеры создайте новый Сервис мессенджеров.
- На этапе Пул отправителей добавьте свой номер.
/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.