n8n — масштабируем получение SMS и уведомлений с нескольких (десятков) SIM-карт одновременно
- понедельник, 29 декабря 2025 г. в 00:00:08
TL;DR Автор в прошлой статье настроил Telegram-чат, куда несколько смартфонов скидывают пуши с помощью MacroDroid и/или Tasker. Проблема в том, что смартфоны брали на себя слишком много работы. Что, если они будут тонкими клиентами, которые шлют сырые данные на сервер, где уже происходит вся обработка и рассылка? Автор делится workflow и конфигурацией для n8n, которые позволяют это реализовать в режиме "Быстрого старта".
Общий дисклеймер • О личности автора • Отказ от ответственности • Об использовании ChatGPT
В статье рассматривается вопрос более гибкого и масштабируемого решения получения уведомлений с нескольких SIM-карт - за счёт некоего центрального сервера, в данном случае n8n. За счёт этого у администратора n8n-сервера появляется возможность полностью динамически настраивать всю бизнес-логику: от обработки поступающих данных и форматирования до рассылки уведомлений в Telegram (и не только). Рассказывается также об истории создания проекта, способах создания конфигурации для него и настройке не только сервера, но и конечных устройств. В данный момент автор использует данный сетап. Рассмотрены вопросы, проблемы и процесс создания всего конвейера - от MacroDroid/Tasker до n8n.
Предположим, что вы смогли-таки реализовать сетап с MacroDroid/Tasker из прошлой статьи. Или вы просто столкнулись с необходимостью масштабировать получение SMS и уведомлений с нескольких десятков SIM-карт одновременно. И, вдобавок, нужно пересылать эти уведомления в Telegram, причём не только себе. Вдобавок, периодически. Вдобавок, срочно, желательно в ту же секунду. Вручную подобным мазохизмом заниматься не хочется, да и не получится. А вот автоматизировать - можно, да и, положа руку на сердце - придётся.
В качестве затравки может прочитать предыдущую статью, где описаны подробные критерии выбора смартфонов и принцип работы с MacroDroid/Tasker. Здесь основной упор будет выполнен на централизацию существующего решения.
Рассматривал следующее:
Написать код на TypeScript (NestJS), упаковать и запустить на сервере, который будет принимать HTTP-запросы от смартфонов и обрабатывать их;
Индустриальный стек мониторинга и алертинга: Prometheus + AlertManager + Grafana + какой-нибудь экспортер SMS/уведомлений. Всё это дело у меня уже имеется, осталось только написать экспортеры или (что более вероятно) HTTP-запросы для PushGateway или VictoriaLogs или Vector;
Третий вариант подкрался неожиданно - ребята раздавали экземпляры n8n бесплатно. Ну, что ж, а раз дело дошло до бесплатного, то почему бы и не попробовать?
n8n - это low-code платформа для автоматизации рабочих процессов. Она позволяет создавать сложные интеграции и автоматизации с минимальным количеством кода, используя визуальный интерфейс. В моём случае это было именно то, что нужно: быстрое создание рабочих процессов для обработки входящих данных и отправки уведомлений.
Что ж, пишем воркфлоу на нём, благо целый парк SIM-карт и телефонов у меня уже имеется.
Кратко: на каждый смартфон поставил MacroDroid или Tasker с плагином AutoNotification, настроил группу с Telegram-ботом, созданным в @BotFather.
Принцип работы следующий, подсмотренный у ядра XRay:
Есть единый конфиг, полностью и почти что в императивном стиле описывающий, что принимать, как обрабатывать, в каком виде рендерить и куда слать. Входящий webhook принимает HTTP-запросы от смартфонов (MacroDroid/Tasker), затем просто происходит цепочка маппингов и темплейтингов, заигрывания с сырыми данными и по конвейеру выдающему последней ноде список адресов и payload, куда и как слать запросы.
Будет несколько сущностей:
Recipients - получатели уведомлений (например, Telegram userId или chatId). Содержит метаданные о получателе, например, разрешённые deviceId (чтобы не слали посторонние смартфоны), предпочтительный язык, дополнительные плейсхолдеры и т.д. Также содержит секреты для API (например, Telegram Bot token) и, самое главное - объект rules. С помощью него можно очень гибко настраивать, какие именно уведомления и при каких условиях этот получатель хочет нужные данные. Например, только SMS с OTP-кодами, только от определённых номеров, только с определёнными ключевыми словами и т.д. Recipients связывает Templates и Endpoints;
Templates - шаблоны сообщений, которые будут рендериться с помощью плейсхолдеров. Например, шаблон для SMS с OTP-кодом может выглядеть так: "Получено SMS от {data.fromPhoneNumber}: {data.body}. Код: {data.otpCodes[0]}". Шаблоны могут быть разными для разных типов уведомлений (SMS, push-уведомления и т.д.);
Endpoints - конечные точки, куда будут отправляться уведомления. Например, для Telegram это будет URL вида https://api.telegram.org/bot{recipient.botToken}/sendMessage, с параметрами chat_id={recipient.chatId} и text={%text%}. Можно также добавить поддержку других платформ, например, Slack, Email и т.д.;
Собственно, всё. Этот конфиг задаёт всё поведение системы от начала и до конца. Но он будет не самым простым, и вам придётся его создать самостоятельно, пока я не написал веб-конструктов для него.
Как истинный ООПшник (кстати, что такое ООП?), решил я начать с проектирования. В моём случае я описал модельки полезной нагрузки (payload) для входящих HTTP-запросов от смартфонов:
export interface SmsReceivedDataModel {
/** Discriminator. */
type: "sms_received";
/** Sender address/number (may be alphanumeric in some regions). */
fromName?: string;
fromNameInContacts?: string;
fromPhoneNumber?: string;
/** Message body text. */
body: string;
/** ISO 8601 timestamp when SMS was received (device time). */
at?: string;
/** SIM slot index if known (1/2). */
simSlot?: number;
/** Subscription/carrier name if available. */
carrier?: string;
/** Message center address if captured. */
serviceCenter?: string;
/** True if message looks like OTP (if your automation tags it). */
isOtp?: boolean;
/** Extracted OTP code(s) if automation parses it. */
otpCodes?: string[];
/** Optional thread id / conversation id from SMS provider. */
threadId?: string;
/** Optional app/package that captured the SMS (some automations rely on notifications). */
capturedByPackage?: string;
/** Optional keyword tags computed by automation app. */
tags?: string[];
}
/**
* Core configuration for an n8n workflow that:
* - receives "data" from an automation app via webhook
* - selects recipients & templates
* - renders messages (templating)
* - sends to external HTTP endpoints (e.g., Telegram Bot API)
*
* All top-level entities are objects (maps) keyed by id to match your preference
* (objects over arrays) and enable stable lookups in n8n.
*/
export interface ConfigModel {
/** Schema version for migrations and backward compatibility. */
version: string;
tokens: Record<string, string>[];
/** Optional human label for the configuration instance. */
name?: string;
/**
* A map of recipients by `id`.
*
* Key MUST match `Recipient.id`.
*/
recipients: Record<RecipientId, RecipientModel>;
/**
* A map of endpoints by `id`.
*
* Endpoints may reference `{recipient.*}` placeholders.
*/
endpoints: Record<EndpointId, EndpointModel>;
/**
* A map of templates by `id`.
*
* Templates may reference placeholders from data/recipient/meta.
*/
templates: Record<TemplateId, TemplateModel>;
/**
* Optional device registry. If omitted, "device checks" can rely solely on deviceId
* strings embedded in incoming payloads and recipients.allowedDeviceIds.
*/
devices?: Record<DeviceId, DeviceDefinition>;
/**
* Optional global defaults applied by n8n when building outbound requests.
* Per-endpoint settings override these defaults.
*/
defaults?: {
/** Default timeout in milliseconds for outbound HTTP calls. */
httpTimeoutMs?: number;
/** Default headers applied to outbound HTTP calls unless overridden. */
headers?: Record<string, string>;
/**
* If true, the system should reject any placeholder that cannot be resolved
* (fail-fast). If false, unresolved placeholders may be left as-is or replaced
* with empty string depending on runtime policy.
*/
strictTemplating?: boolean;
};
}
/**
* Core configuration for an n8n workflow that:
* - receives "data" from an automation app via webhook
* - selects recipients & templates
* - renders messages (templating)
* - sends to external HTTP endpoints (e.g., Telegram Bot API)
*
* All top-level entities are objects (maps) keyed by id to match your preference
* (objects over arrays) and enable stable lookups in n8n.
*/
export interface ConfigModel {
/** Schema version for migrations and backward compatibility. */
version: string;
tokens: Record<string, string>[];
/** Optional human label for the configuration instance. */
name?: string;
/**
* A map of recipients by `id`.
*
* Key MUST match `Recipient.id`.
*/
recipients: Record<RecipientId, RecipientModel>;
/**
* A map of endpoints by `id`.
*
* Endpoints may reference `{recipient.*}` placeholders.
*/
endpoints: Record<EndpointId, EndpointModel>;
/**
* A map of templates by `id`.
*
* Templates may reference placeholders from data/recipient/meta.
*/
templates: Record<TemplateId, TemplateModel>;
/**
* Optional device registry. If omitted, "device checks" can rely solely on deviceId
* strings embedded in incoming payloads and recipients.allowedDeviceIds.
*/
devices?: Record<DeviceId, DeviceDefinition>;
/**
* Optional global defaults applied by n8n when building outbound requests.
* Per-endpoint settings override these defaults.
*/
defaults?: {
/** Default timeout in milliseconds for outbound HTTP calls. */
httpTimeoutMs?: number;
/** Default headers applied to outbound HTTP calls unless overridden. */
headers?: Record<string, string>;
/**
* If true, the system should reject any placeholder that cannot be resolved
* (fail-fast). If false, unresolved placeholders may be left as-is or replaced
* with empty string depending on runtime policy.
*/
strictTemplating?: boolean;
};
}
/* ----------------------------- IDs / Aliases ----------------------------- */
export type RecipientId = string;
export type EndpointId = string;
export type TemplateId = string;
export type DeviceId = string;
/* -------------------------------- Recipient ------------------------------ */
/**
* A "recipient" is a business-logic target that receives templated messages via one
* or more endpoints. Example: a Telegram user/channel/chat ID plus extra vars.
*/
export interface RecipientModel {
/** Optional display name for UI or logs. */
name?: string;
disabled?: boolean;
/**
* Recipient-scoped variables for templating, e.g.:
* { "tgChatId": "123456", "lang": "ru", "timezone": "Asia/Bangkok" }
*
* Use `{recipient.vars.tgChatId}` (or `{recipient.vars.lang}`) in templates/endpoints.
*/
vars: Record<string, string | number | boolean | null>;
/**
* Which incoming devices are allowed to trigger messages to this recipient.
* If omitted or empty, all devices are allowed (unless your runtime enforces otherwise).
*/
allowedDeviceIds?: DeviceId[];
/**
* Which endpoint(s) the recipient is eligible to send through.
* If omitted or empty, runtime may allow all endpoints or require explicit mapping
* depending on your policy.
*/
endpointIds?: EndpointId[];
/**
* Which template(s) may be used for this recipient.
* If omitted or empty, runtime may allow all templates or require explicit mapping
* depending on your policy.
*/
templateIds?: TemplateId[];
/**
* Optional filtering by data types for this recipient.
* If omitted, all data types are allowed.
*/
allowedDataTypes?: DataType[];
/**
* Optional per-recipient rendering preferences.
* Useful for future UI and consistent formatting.
*/
rendering?: {
/** Language preference for template selection, if you implement i18n. */
language?: string;
/** Timezone for formatting timestamps. */
timezone?: string;
/** If true, escape HTML (Telegram HTML mode) or perform endpoint-specific escaping. */
escapeMode?: "none" | "telegram_html" | "telegram_markdownv2";
};
/**
* Optional "routing rules" to select specific template(s) and/or endpoint(s)
* based on incoming data. This enables business logic in config instead of n8n code.
*/
rules: RecipientRuleModel[];
}
/**
* A rule evaluated in order; first match wins (typical policy).
*/
export interface RecipientRuleModel {
/** Unique id of the rule (for UI, debugging, and test referencing). */
id: string;
/** Optional human description. */
description?: string;
type: DataType;
/** Match conditions; all specified conditions must match (AND). */
when: FieldPredicate[];
/**
* Actions to apply when the rule matches.
* These override or constrain the recipient's default templateIds/endpointIds.
*/
then: {
/** Use these templates (in order). */
templateIds?: TemplateId[];
/** Send via these endpoints (in order). */
endpointIds?: EndpointId[];
};
}
/**
* A simple field predicate for config-driven routing.
*/
export interface FieldPredicate {
/** Path in the runtime object (you define actual resolver). */
path: string;
/** Comparison operator. */
operator:
| "equals"
| "not_equals"
| "includes"
| "not_includes"
| "starts_with"
| "ends_with"
| "matches_regex"
| "gt"
| "gte"
| "lt"
| "lte"
| "exists";
/** Comparison value; omitted for `exists`. */
value?: string | number | boolean | null;
}
/* -------------------------------- Endpoint -------------------------------- */
/**
* Defines how to send an HTTP request.
* URLs, headers, query, and body may include `{recipient.*}` placeholders.
*
* NOTE: Actual auth secrets should ideally NOT be embedded directly here unless
* encrypted or stored in n8n credentials; model allows it for completeness.
*/
export interface EndpointModel {
/** Optional display name for UI or logs. */
name?: string;
/** HTTP method. */
method: HttpMethod;
/**
* Fully qualified URL including path.
* May contain templated placeholders from recipient vars, e.g.:
* "https://api.telegram.org/bot{recipient.vars.botToken}/sendMessage"
*/
url: string;
/**
* Optional query string parameters.
* Values may include placeholders.
*/
searchParams?: Record<string, string>;
/**
* Optional headers.
* Values may include placeholders.
*/
headers?: Record<string, string>;
/**
* Optional request body settings.
* If not provided, runtime can send no body.
*/
body?: EndpointBody;
/**
* Timeout in milliseconds for this endpoint call.
* Overrides config.defaults.httpTimeoutMs.
*/
timeoutMs?: number;
/**
* Optional retry policy for transient failures.
*/
retry?: {
/** Maximum attempts including the first try. */
maxAttempts: number;
/** Backoff strategy. */
backoff: "fixed" | "exponential";
/** Base delay in milliseconds. */
baseDelayMs: number;
/** Only retry on these HTTP status codes, if specified. */
retryOnStatusCodes?: number[];
};
/**
* Optional expectation/validation on response for success determination.
* Useful for endpoints like Telegram that always return JSON with ok=true/false.
*/
successCriteria?: {
/** Treat these status codes as success. If omitted, use 200-299. */
statusCodes?: number[];
/**
* A JSON path to a boolean "success" field.
* Example for Telegram: "ok"
*/
jsonBooleanPath?: string;
};
}
export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
export interface EndpointBody {
/**
* Body format.
* - json: typical REST payload
* - form_urlencoded: Telegram often supports application/x-www-form-urlencoded
* - raw: string payload (e.g., pre-rendered JSON or text)
*/
type: "json" | "form_urlencoded" | "raw";
/**
* Body content.
* - For json/form_urlencoded: object where leaf values may contain placeholders
* - For raw: a string which may contain placeholders
*/
content: Record<string, unknown> | string;
}
/* -------------------------------- Templates ------------------------------- */
/**
* A template is a named message payload with placeholders.
* You can keep it generic, or add endpoint-specific "rendering targets" later.
*/
export interface TemplateModel {
/** Optional human name for UI/logs. */
name?: string;
/**
* Main template string. Placeholders use `{...}` and are resolved at runtime.
*
* Recommended placeholder namespaces:
* - {data.*} incoming data payload fields
* - {recipient.*} recipient fields/vars
* - {meta.*} envelope info (deviceId, receivedAt, etc.)
*/
text: string;
/**
* Optional endpoint-specific formatting metadata.
* Example: Telegram sendMessage parse_mode.
*/
format?: {
/** Controls how the receiving endpoint should parse markup. */
parseMode?: "plain" | "telegram_html" | "telegram_markdownv2";
/** If true, strip unknown placeholders (otherwise fail or keep). */
ignoreUnknownPlaceholders?: boolean;
};
/**
* Optional additional fields that can be used as payload parts
* (e.g., title, subject, caption), depending on endpoint mapping.
*/
fields?: Record<string, string>;
}
/* --------------------------------- Devices -------------------------------- */
/**
* Optional device registry entry to enrich logs and enable per-device gating.
*/
export interface DeviceDefinition {
/** Optional friendly name. */
name?: string;
/** Optional platform description. */
platform?: "android" | "ios" | "other";
/** Optional additional metadata. */
vars?: Record<string, string | number | boolean | null>;
}
Да, вышло заморочно-таки, особенно со второй моделькой. Да, писалось нейронкой, но затем на 60% отредактировано мной.
Первая версия писалась очень долго. Решил я тогда однозначно всё-таки вкатиться в вайб-кодинг, причём по уму: настроить проект в VS Code, nx monorepo (ибо будет несколько проектов), copilot-instructions.md/AGENTS.md, MCP, Skills и т. д.
Кто не шарит за вайб-код, MCP - это типа как API внешних инструментов для ИИ-агентов на основе нейросетей. В данном случае оно поможет нейронке правильно создавать и редактировать workflow у n8n. Для MCP с n8n есть классный проект n8n-mcp, его и подключаем для Windows через npm:
npm i n8n-mcp -g
В mcp.json прописываем (не забудьте подставить свои значения):
{
"mcp": {
"servers": {
"n8n-mcp": {
"command": "npx",
"args": ["n8n-mcp"],
"env": {
"N8N_API_URL": "http://localhost:5678",
"N8N_API_KEY": "your-api-key"
}
}
}
}
}
После этого можно создавать и редактировать workflow в n8n с помощью нейронки. Грузанул её контекстом, моделями, привёл аналогии, простые примеры, чего я хочу. Промпт бы потянул на тонкую книжку!
Но, к сожалению, нейросети, даже хвалёные GPT-5.2 и Gemini 3 Pro, создавали лишь что-то типа такого:

Не, ну так можно было и на моём любимом TypeScript написать. Вся бизнес-логика в одной ноде, ей Б-гу! Отхохотавшись вволю, решил писать сам по-старинке, без вайб-кодинга, но местами с помощью нейронки.
P. S. Надо отдать должное - всё завелось с первого раза, но смысл тогда был в n8n, если мне от него нужна была одна нода со всей бизнес-логикой? Так не пойдёт. Дальше пишем сами.
Нейросети не пользовались фичами n8n, поэтому начал делить 9К строк кода на отдельные ноды, каждая из которых выполняет одну функцию:

Добавим ветвящуюся логику, поддержку GET- и POST-запросов:

Выделим внутреннюю проверку ошибок в If-ноды, будем выкидывать разные ошибки - от авторизации до валидации и маппинга.
Итоговый workflow получился таким:

Теперь можно играться как угодно с маппингом данных, их содержимым, фильтровать входящие уведомления об СМС или пушах и отправлять как угодно в каком угодно виде. Всё сделано так, что вам здесь нужно изменить всего лишь одну ноду:

- и оно будет делать как описано.
Каждое устройство имеет свой токен-ключ, без них n8n не пропустит левые запросы. Для дебага с HTTP-клиента можно добавить токен с именем debug. Токены придумываете сами, сохраняете в конфиге n8n. Смартфоны обязаны использовать ключи в HTTP-заголовке x-n8n-device-relay.
Ключевую роль играет шаблонизация. Реализована она в виде таких строк: {data.body} или {device.id} - они позволяют ссылаться куда и как угодно. По сути, вы можете не соблюдать мои гайдлайны и практически весь конфиг переделать под свои модельки и слать с мобилок любые JSON'ки - бизнес-логика сама позаботится о правильном рендеринге значений, если вы всё сделали правильно.
Честно, не хотел изобретать такой велосипед с шаблонизацией, но я не нашёл возможности прикрутить, например, Jinja в n8n (особенно учитывая, что он не мой). Поэтому попросил нейронку написать простую шаблонизацию на JavaScript, который и использую в одной из нод. Работает вполне себе нормально - можно ссылаться на вложенные объекты, но, кажется, массивы оно не поддерживает.
Итоговый конфиг выглядит примерно так:
{
"version": "1.0.0",
"tokens": [
{
"name": "myphone",
"key": "<THINK_OF_A_SECRET_KEY_YOURSELF>"
},
{
"name": "debug",
"key": "<THIS_TOKEN_WITH_DEBUG_NAME_BYPASSES_DEVICE_CHECK_USE_ONLY_FOR_TESTING>"
}
],
"recipients": {
"me": {
"vars": {
"botToken": "<INSERT_TELEGRAM_BOT_TOKEN>",
"tgChatId": "<INSERT_TELEGRAM_CHAT_ID>"
},
"rules": [
{
"id": "rule_sms_received",
"type": "sms_received",
"when": [
{
"path": "device.id",
"operator": "equals",
"value": "myphone"
}
],
"then": {
"endpointIds": [
"telegram_sendMessage_post"
],
"templateIds": [
"template_sms_1_ru"
]
}
},
{
"id": "rule_call_received",
"type": "call_received",
"when": [
{
"path": "device.id",
"operator": "equals",
"value": "myphone"
}
],
"then": {
"endpointIds": [
"telegram_sendMessage_post"
],
"templateIds": [
"template_call_1_ru"
]
}
},
{
"id": "rule_wifi_connected",
"type": "wifi_connected",
"when": [
{
"path": "device.id",
"operator": "equals",
"value": "myphone"
}
],
"then": {
"endpointIds": [
"telegram_sendMessage_post"
],
"templateIds": [
"template_wifi_1_ru"
]
}
},
{
"id": "rule_notification_received",
"type": "notification_received",
"when": [
{
"path": "device.id",
"operator": "equals",
"value": "myphone"
}
],
"then": {
"endpointIds": [
"telegram_sendMessage_post"
],
"templateIds": [
"template_app_1_ru"
]
}
},
{
"id": "rule_notification_received",
"type": "notification_received",
"when": [
{
"path": "device.id",
"operator": "equals",
"value": "myphone"
},
{
"path": "data.body",
"operator": "matches_regex",
"value": "(?:.+)?SOME STRING(?:.+)?"
}
],
"then": {
"endpointIds": [
"telegram_sendMessage_post"
],
"templateIds": [
"template_app_1_ru"
]
}
}
]
}
},
"endpoints": {
"telegram_sendMessage_get": {
"method": "GET",
"url": "https://api.telegram.org/bot{recipient.vars.botToken}/sendMessage",
"searchParams": {
"chat_id": "{recipient.vars.tgChatId}",
"text": "{%text%}"
}
},
"telegram_sendMessage_post": {
"method": "POST",
"url": "https://api.telegram.org/bot{recipient.vars.botToken}/sendMessage",
"body": {
"type": "json",
"content": {
"chat_id": "{recipient.vars.tgChatId}",
"text": "{%text%}"
}
}
}
},
"templates": {
"template_sms_1_ru": {
"text": "📄 Входящее смс от: {data.fromName} (номер телефона: {data.fromPhoneNumber})\n\n📖 Текст:\n{data.body}\n\nℹ️ Доп. данные:\n🔋 Заряд: {device.batteryLevelPercent}%,\n📟 Имя SIM: {device.cellularCarrierName} (ID: {device.currentSimSlotIndex}),\n📶 Сеть Wi-Fi: {device.currentWifiSsid}"
},
"template_call_1_ru": {
"text": "📞 Входящий звонок от: {data.fromName} (номер телефона: {data.fromPhoneNumber})\n\nℹ️ Доп. данные:\n🔋 Заряд: {device.batteryLevelPercent}%,\n📟 Имя SIM: {device.cellularCarrierName} (ID: {device.currentSimSlotIndex}),\n📶 Сеть Wi-Fi: {device.currentWifiSsid}"
},
"template_wifi_1_ru": {
"text": "🛜 Сеть Wi-Fi подключена!\n\nℹ️ Доп. данные:\n🔋 Заряд: {device.batteryLevelPercent}%"
},
"template_app_1_ru": {
"text": "🔔 Уведомление от приложения: {data.appName} ({data.packageName})\n\n📖 Заголовок:\n{data.title}\n\n📖 Текст:\n{data.body}\n\nℹ️ Доп. данные:\n🔋 Заряд: {device.batteryLevelPercent}%"
}
}
}
Как вам?) Давайте разберём его подробнее. Надеюсь, вы зацените результат.
Начинаем строить JSON'ку! Пишите:
{
}
- всё нижеперечисленное пихаем внутрь.
version - версия схемы конфига. Это не ваша версия, а служебное значение. Пока что 1.0.0. Конкретно сейчас роли не играет, но в будущем может понадобиться для миграций и обратной совместимости.
"version": "1.0.0",
tokens - список токенов-ключей для авторизации устройств. Каждый токен имеет имя и ключ. Каждый смартфон должен использовать свой ключ в заголовке x-n8n-device-relay. Для дебага предусмотрен токен с именем debug, который пропускает проверку устройства.
"tokens": [
{
"name": "myphone",
"key": "<THINK_OF_A_SECRET_KEY_YOURSELF>"
},
{
"name": "debug",
"key": "<THIS_TOKEN_WITH_DEBUG_NAME_BYPASSES_DEVICE_CHECK_USE_ONLY_FOR_TESTING>"
}
],
recipients - карта получателей уведомлений. Каждый получатель имеет свои переменные, правила и настройки. В примере есть получатель с id me, который хранит в себе переменные botToken и tgChatId для отправки им сообщений в Telegram.
"recipients": {
"me": {
"disabled": true,
"vars": {
"botToken": "<INSERT_TELEGRAM_BOT_TOKEN>",
"tgChatId": "<INSERT_TELEGRAM_CHAT_ID>"
},
"allowedDeviceIds": [
"myphone"
],
"endpointIds": [
"telegram_sendMessage_post"
],
"templateIds": [
"template_sms_1_ru",
"template_call_1_ru",
"template_wifi_1_ru",
"template_app_1_ru"
],
"rules": [
{
"id": "rule_sms_received",
"type": "sms_received",
"when": [
{
"path": "device.id",
"operator": "equals",
"value": "myphone"
}
],
"then": {
"endpointIds": [
"telegram_sendMessage_post"
],
"templateIds": [
"template_sms_1_ru"
]
}
}
]
}
},
Разберём его подробнее:
vars - переменные получателя, которые можно использовать в шаблонах templates и конечных точках endpoints. В данном случае это токен бота и ID чата Telegram. Вы можете добавить любые другие переменные по своему усмотрению и использовать их затем в шаблонах и эндпоинтах (будут описаны нижу);
rules - список правил для данного получателя. Каждое правило имеет:
Уникальный в пределах recipient'а id;
Тип данных type (например, sms_received, call_received, wifi_connected, notification_received);
Условия when. В условиях можно указать путь к полю, оператор (equals, not_equals, includes, not_includes, starts_with, ends_with, matches_regex, gt, gte, lt, lte, exists) и значение. Особенно полезен оператор matches_regex для фильтрации уведомлений по ключевым словам, в значении (value) вы указываете регулярное выражение без косых черт //и модификаторов типа gmi;
Действия then. В действиях указываются идентификаторы шаблонов и конечных точек, которые будут использоваться при совпадении условия;
Важно! Внутри правила всё в массиве when работает по логике И (AND) - все условия должны быть выполнены для срабатывания правила.
Опционально можно добавить т. н. "белые списки" устройств (allowedDeviceIds), шаблонов (templateIds) и конечных точек (endpointIds) на уровне получателя, чтобы ограничить доступ. Они переопределяют rules. Например, если в rules есть конечная точка telegram_sendMessage_post, но allowedEndpointIds существует и этого эндпоинта там нет, то правило не выполнится;
Иногда клиент нужен, но не сейчас. Тогда ставим disabled: true, и recipient будет отключён. Поле опционально, поэтому можно убрать его вообще.
endpoints - карта конечных точек для отправки уведомлений. В примере есть две конечные точки для отправки HTTP-запросов (пока что по дефолту - только Telegram, но можете добавить что угодно своё). Поддерживаются методы GET и POST.
"endpoints": {
"telegram_sendMessage_get": {
"method": "GET",
"url": "https://api.telegram.org/bot{recipient.vars.botToken}/sendMessage",
"searchParams": {
"chat_id": "{recipient.vars.tgChatId}",
"text": "{%text%}"
}
},
"telegram_sendMessage_post": {
"method": "POST",
"url": "https://api.telegram.org/bot{recipient.vars.botToken}/sendMessage",
"body": {
"type": "json",
"content": {
"chat_id": "{recipient.vars.tgChatId}",
"text": "{%text%}"
}
}
}
},
Сразу можно заметить, что в URL и параметрах используются плейсхолдеры вида {recipient.vars.botToken} и {%text%}. Первый - это переменная получателя, а второй - это текст, сгенерированный из шаблона. {%text%} - специальный "магический" плейсхолдер, смотрящий в подходящий шаблон (так как движок рендеринга значений смотрит лишь в data, а в templates у него вход заказан).
templates - шаблоны (no shit Sherlock!) текста. В примере есть несколько шаблонов для разных типов уведомлений (SMS, звонки, подключение к Wi-Fi, уведомления приложений). Каждый шаблон имеет текст с плейсхолдерами для динамического заполнения.
"templates": {
"template_sms_1_ru": {
"text": "📄 Входящее смс от: {data.fromName} (номер телефона: {data.fromPhoneNumber})\n\n📖 Текст:\n{data.body}\n\nℹ️ Доп. данные:\n🔋 Заряд: {device.batteryLevelPercent}%,\n📟 Имя SIM: {device.cellularCarrierName} (ID: {device.currentSimSlotIndex}),\n📶 Сеть Wi-Fi: {device.currentWifiSsid}"
},
"template_call_1_ru": {
"text": "📞 Входящий звонок от: {data.fromName} (номер телефона: {data.fromPhoneNumber})\n\nℹ️ Доп. данные:\n🔋 Заряд: {device.batteryLevelPercent}%,\n📟 Имя SIM: {device.cellularCarrierName} (ID: {device.currentSimSlotIndex}),\n📶 Сеть Wi-Fi: {device.currentWifiSsid}"
},
"template_wifi_1_ru": {
"text": "🛜 Сеть Wi-Fi подключена!\n\nСеть Wi-Fi: {device.currentWifiSsid}\nДанные о локации:\n- {device.locationPoint}\n- {device.locationLink}\n- Ссылка на последнее известное местоположение: {device.locationLastTimestamp}\n- Скорость аппарата: {device.currentSpeed} км/ч.\n\nℹ️ Доп. данные:\n🔋 Заряд: {device.batteryLevelPercent}%"
},
"template_app_1_ru": {
"text": "🔔 Уведомление от приложения: {data.appName} ({data.packageName})\n\n📖 Заголовок:\n{data.title}\n\n📖 Текст:\n{data.body}\n\nℹ️ Доп. данные:\n🔋 Заряд: {device.batteryLevelPercent}%"
}
}
Тут уже довольно просто. Можете использовать переменные, самих шаблонов делаете сколько угодно. Используете их в recipient'ах, в объекте rules.
Как только конфиг будет готов, переходим к следующему шагу.
Всё сделано до вас: копипастите этот workflow (raw) в свой n8n, Ctrl + V в окне с новым workflow.
Видите этот участок:

- два раза там кликаете по ноде Config и вставляете свою JSON'ку.
С помощью HTTP-клиента и моей коллекции Postman можете протестировать работу workflow:
Выбираете нужный запрос в HTTP-клиенте;
В environment не забудьте вбить адрес вашего n8n-сервера и ключ устройства;
Во вкладке с n8n наведите на Webhook и жмите Execute workflow;
Жмёте Send в HTTP-клиенте;
Видите, что workflow отработал или нет, и почему.
И, если вздумается расширить логику, вы можете свободно можете менять содержимое нод, смотреть, как маппятся данные, что на входе и на выходе. Добавьте свои конечные точки после ноды Switch.
Настраиваем смартфоны с MacroDroid или Tasker:
Скачать макросы для MacroDroid или профили для Tasker (для последнего не забудьте установить плагин AutoNotification);
Импортировать их в MacroDroid/Tasker на каждом смартфоне:
У MacroDroid это реализовано через главное меню - Импорт/Экспорт макросов - Импорт макросов;
У Tasker это реализовано через Профили. Удерживаете палец на вкладке Профили, выбираете Импорт;
В настройках каждого макроса/профиля указать в header x-n8n-device-relay ключ устройства из вашего конфига n8n;
В настройках каждого макроса/профиля указать URL вашего n8n-сервера (пример: http://your-n8n-domain.com/webhook/device-relay).
Тестируйте, проверяйте результат во вкладке n8n под названием executions.
Вы можете быстро сгенерировать MacroDroid макросы для каждого устройства скриптом PowerShell, для этого:
Клонируем репозиторий, в терминале переходим в его папку;
Копируем .\scripts\device-list.example.json или .\scripts\device-list-minimal.example.json с новым именем .\scripts\device-list.json:
Copy-Item .\scripts\device-list.example.json .\scripts\device-list.json
- редактируем его, добавляя свои устройства. Токены у устройств должны совпадать с токенами в конфиге n8n;
Пишем:
.\scripts\generate-macrodroid-macros-per-device.ps1
В папке .\scripts\devices\ появятся готовые макросы для MacroDroid, которые останется только импортировать в MacroDroid на каждом устройстве.
В общем-то, да. На самом деле, в модельке конфига много какие поля описаны, просто абсолютное большинство опциональны. Это всё потому, что те же приложения-автоматизаторы имеют очень слабо пересекающиеся множества данных, и привести их к общему знаменателю, единой модели, сложно. Как мог...
В будущем надо бы на опциональный bcrypt перейти и задуматься о поддержке iOS Shortcuts и Android Automate, но это уже другая история. Я лично счастлив с MacroDroid и Tasker.
Проблема: Чому не самохостинг?
Решение: Я ждал этот вопрос. Знаете, это личное и, по-моему мнению, логичное решение, которое я описывал в статье "Чему учит постоянная релокация (или её ожидание) в контексте персональной инфраструктуры". Кратко говоря: когда ты на ходу и не нашёл свой дом, то лучше избегать самохостинга максимально. n8n в облаке или чей-то чужой n8n в данном случае - это просто в использовании. Хорошо, что оно подвернулось под руку.
Проблема: Ты палишь токены в открытом виде в конфиге! Шлёшь данные со смартфонов открытым текстом! Админ это видит, админ это сливает!
Решение: Ага. "А что ты мне сделаешь, я в другмо городе" ©. Это просто вопрос доверия к администратору n8n-сервера или надежде на принцип "неуловимого Джо". Админу с рутовыми правами ничего не мешает грепать логи n8n килограммами, или веб-сервер на verbose-логи настроить, или даже напрямую лезть в БД или var/log и кушать данные там, хех. Ладно, ладно! Давайте договоримся: в будущем планируется предварительная обработка данных bcrypt'ом - например, прямо на смартфоне (вряд ли), на своём сервере или на Cloudflare Worker'е. PR welcome.
Проблема: Так-то это можно реализовать на Vector/VictoriaLogs/PushGateway + Prometheus + AlertManager, зачем городить огород с n8n?
Решение: Я бы так и сделал). Правда, по моделькам и авторизации как у них - я не знаю. Вдобавок, я прям вижу, как вспухли бы правила алертинга у Прометея, там ведь фильтровать и маппить всё это надо. n8n мне попался чисто случайно, и я решил попробовать. Так-то подобное можно реализовать на любом бэкенде, было бы желание. У меня вышло неплохо, собой доволен.
Таким образом, с помощью этой статьи вам удастся масштабировать получение SMS и уведомлений с парка смартфонов огромного размера (IMO до сотен или даже тысяч аппаратов), используя n8n в качестве центрального сервера для обработки и рассылки уведомлений. Вы сможете настроить гибкую бизнес-логику, шаблоны сообщений и конечные точки доставки, что позволит вам эффективно управлять большим количеством устройств и получать важные уведомления в нужное время и в нужном формате. Развитие системы планируется в сторону поддержки дополнительных платформ и улучшения безопасности передачи данных.