TL;DR Автор в прошлой статье настроил Telegram-чат, куда несколько смартфонов скидывают пуши с помощью MacroDroid и/или Tasker. Проблема в том, что смартфоны брали на себя слишком много работы. Что, если они будут тонкими клиентами, которые шлют сырые данные на сервер, где уже происходит вся обработка и рассылка? Автор делится workflow и конфигурацией для n8n, которые позволяют это реализовать в режиме "Быстрого старта".
Дисклеймеры
Общий дисклеймер • О личности автора • Отказ от ответственности • Об использовании ChatGPT
Аннотация
В статье рассматривается вопрос более гибкого и масштабируемого решения получения уведомлений с нескольких SIM-карт - за счёт некоего центрального сервера, в данном случае n8n. За счёт этого у администратора n8n-сервера появляется возможность полностью динамически настраивать всю бизнес-логику: от обработки поступающих данных и форматирования до рассылки уведомлений в Telegram (и не только). Рассказывается также об истории создания проекта, способах создания конфигурации для него и настройке не только сервера, но и конечных устройств. В данный момент автор использует данный сетап. Рассмотрены вопросы, проблемы и процесс создания всего конвейера - от MacroDroid/Tasker до n8n.
Введение
Предположим, что вы смогли-таки реализовать сетап с MacroDroid/Tasker из прошлой статьи. Или вы просто столкнулись с необходимостью масштабировать получение SMS и уведомлений с нескольких десятков SIM-карт одновременно. И, вдобавок, нужно пересылать эти уведомления в Telegram, причём не только себе. Вдобавок, периодически. Вдобавок, срочно, желательно в ту же секунду. Вручную подобным мазохизмом заниматься не хочется, да и не получится. А вот автоматизировать - можно, да и, положа руку на сердце - придётся.
В качестве затравки может прочитать предыдущую статью, где описаны подробные критерии выбора смартфонов и принцип работы с MacroDroid/Tasker. Здесь основной упор будет выполнен на централизацию существующего решения.
Почему n8n?
Рассматривал следующее:
Написать код на 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-запросов от смартфонов:
Например, SMS-сообщение
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[]; }
Конфиг для n8n workflow
/** * 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% отредактировано мной.
Первая версия workflow для n8n
Первая версия писалась очень долго. Решил я тогда однозначно всё-таки вкатиться в вайб-кодинг, причём по уму: настроить проект в 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
version - версия схемы конфига. Это не ваша версия, а служебное значение. Пока что 1.0.0. Конкретно сейчас роли не играет, но в будущем может понадобиться для миграций и обратной совместимости.
"version": "1.0.0",
tokens
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
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
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
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.
Как только конфиг будет готов, переходим к следующему шагу.
Я приготовил n8n, сделал конфиг - но это не workflow. Где его взять?
Всё сделано до вас: копипастите этот 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 в качестве центрального сервера для обработки и рассылки уведомлений. Вы сможете настроить гибкую бизнес-логику, шаблоны сообщений и конечные точки доставки, что позволит вам эффективно управлять большим количеством устройств и получать важные уведомления в нужное время и в нужном формате. Развитие системы планируется в сторону поддержки дополнительных платформ и улучшения безопасности передачи данных.
