Spintax для n8n: уникальный текст на лида — и на товар
В n8n ваш список лидов или товарный фид уже лежит строками. n8n-nodes-spintax добавляет одну ноду, которая превращает каждую строку в готовый текст: холодное письмо на лида, описание товара на витрину — тот же поток, другой фид. Этот гайд проведёт по шагам: установка, первый рендер, мост к рассылке, товарный вариант и цикл, где шаблон пишет ваша LLM.
Один поток, два фида
Всё ниже держится на одном правиле: каждое скалярное поле входящей строки — строка, число, булево — уже %переменная%, ничего маппить не нужно. Подайте потоку список лидов — он напишет тему и тело на каждого лида. Подайте товарный фид — напишет описание на каждый товар. Актив — это шаблон; фид взаимозаменяем.
Установите ноду
В своём n8n откройте Settings → Community Nodes → Install, введите n8n-nodes-spintax, подтвердите. На этом установка кончилась: движок @spintax/core собран внутрь ноды, ключи ей не нужны, наружу вашего инстанса она не ходит. (Community-ноды ставятся там, где это разрешил администратор инстанса; в каталоге n8n Cloud показываются верифицированные ноды.)
Если удобнее начать с готовой канвы — импортируйте сценарий через ⋯ → Import from URL…: рядом с нодой, в её репозитории, лежат два, оба проверены на живом n8n перед выпуском:
- Cold-email bridge — лиды (или товарный фид) на входе, уникальные тема и тело на строку на выходе.
- AI authoring funnel — шаблон пишет ваша LLM; поток его проверяет, чинит и рендерит.
Первый рендер
- Добавьте ноду Spintax. Операция уже стоит Render.
- Вставьте шаблон в поле Template:
{Quick|One|Small} {question|idea} for %company% — the %product% {listing|page}
- Прогоните её по строкам с полями
companyиproduct— Google-таблица, выгрузка из CRM, что угодно, что даёт строки.
Каждая строка выйдет с полем rendered (переименуйте в Output Field, если нужно):
Brightline Gear → "Small idea for Brightline Gear — the Trekker 45L backpack page"
Nordic Home → "Quick question for Nordic Home — the cast-iron skillet set page"
Строки вышли разными, потому что в поле Seed стояло выражение {{ $json.company }}:subject: каждая строка тянет свой вариант, и вариант стабилен — перезапустите сценарий, и ничей текст не перетасуется, так что вы всегда можете показать, что именно получил конкретный лид. Поменяйте суффикс, когда захотите перегенерировать всех. Оставьте Seed пустым — будет свежий розыгрыш на каждый запуск.
Подключите к рассылке
Зачем вообще рендерить в n8n? Затем, что сервис отправки этого не сделает: Instantly, Smartlead и остальные понимают в лучшем случае плоский {a|b|c}, а перестановки, условия и согласование с числом поднимают одно замеренное письмо в пять строк с 59 049 комбинаций до 8,5 миллионов. Поэтому сначала рендер, потом отправка готового текста. Мост — три ноды:
- Ваш источник лидов — Google Sheets, Airtable, выгрузка из CRM.
- Две ноды Render: одна пишет
subject, вторая —body, у каждой seed на строку, как выше. - Нода вашего сервиса отправки — прокиньте в неё
subjectиbody, готово.
Сценарий cold-email bridge из списка выше — ровно это, уже собранное: замените его демонстрационную Code-ноду на свой источник.
Тот же поток, товарный фид
Теперь замените список лидов товарным фидом. Больше не меняйте ничего — но дайте шаблону поля, которые есть у каталога. Вставьте этот:
{?has_discount?По акции за %price% |За %price% }%product% {стоит|пора} {обновить|переписать} — на складе %in_stock% {plural %in_stock%: единица|единицы|единиц}.
in_stock: 1, has_discount: (пусто)
→ «За 7 900 ₽ набор чугунных сковород пора обновить — на складе 1 единица.»
in_stock: 14, has_discount: yes
→ «По акции за 11 500 ₽ рюкзак Trekker 45L пора обновить — на складе 14 единиц.»
Прочитайте, что произошло: фраза про акцию есть только у строк с заполненным has_discount, а «1 единица» никогда не выйдет «1 единиц». Вы не собирали if-ноду и не проверяли грамматику — условие и форма числа живут в самом шаблоне. Одна настройка обязательна: поставьте в ноде Render Locale = ru — без неё трёхформное {plural …} не сработает и останется в тексте в защитных скобках (в авторском цикле локаль приедет сама через spintaxMeta).
Продаёте один каталог на нескольких витринах? Прогоните фид по разу на сайт и положите сайт в seed: {{ $json.sku }}:site-a, потом :site-b. Каждая витрина получит свой стабильный вариант каждого описания — одни данные, везде разный текст, и защита от санкций за дублированный контент становится механической. Почему шаблонная вариативность выигрывает у «пусть модель напишет каждую страницу» — в Что такое спинтакс?; во что обходится путь через модель — в Сколько стоит AI-контент.
Дайте LLM написать шаблон
Богатый шаблон руками — та часть, которую все пропускают. Не пишите его: импортируйте сценарий AI authoring funnel и подключите ровно одно — креды на его ноде модели, либо замените её любой LLM-нодой, которой уже пользуетесь. Ноды Spintax не привязаны к провайдеру: они отдают модели обычный текст systemPrompt/userPrompt и читают обычный текст обратно.
Впишите бриф («короткое письмо для %first_name% из %company%…»), перечислите переменные, которыми модели можно пользоваться — для флективного языка у каждой можно указать падеж, — и запустите. Build Authoring Prompt соберёт канонический промпт, общий для всей экосистемы spintax (версия — в promptVersion). Validate направит черновик в выход Valid или Invalid; на Invalid Build Repair Prompt ткнёт модель в конкретные строку и колонку ошибки и отправит обратно — сценарий ограничивает этот круг одним заходом, чего обычно хватает. На Valid Render Many отдаст пять вариантов на вычитку.
Три настройки, которые стоит знать до запуска:
- Clean Model Output — включите на Validate. Модели заворачивают ответ в кодовые заборы, что бы ни было в промпте; опция срежет их в
cleanedTemplate, и каждая позиция в диагностиках укажет именно в этот текст. - Locale — задайте один раз, в Build Authoring Prompt. Она поедет на строке как
spintaxMeta, и все ноды ниже возьмут её оттуда: поля Locale у Validate, Render и Render Many оставьте пустыми. (Validate прокидываетspintaxMetaна оба своих выхода; ваша LLM-нода отбрасывает незнакомые поля — именно поэтому цепочка не рвётся.) - Count у Render Many (по умолчанию 5, до 100, плюс бюджет попыток Max Attempts, где 0 — автоматически) — и читайте
producedрядом сrequestedв выходе. Разные seed — это независимые розыгрыши, а не гарантированно разные результаты: у шаблона с малой вариативностью может просто не быть пяти вариантов —{Fast|Quick} deliveryдаёт два, сколько ни проси, — и нода скажет об этом прямо, вместо бесконечных повторов или молчаливой недопоставки. - Base Seed — если сгенерированное вы храните. Тогда каждый вариант несёт сид, которым он получен, в поле
attemptSeed: сохраняйте его вместе с текстом. Это не номер строки — как только два розыгрыша совпали, счётчик попыток и позиция в массиве расходятся, и сид остаётся единственным, чем этот конкретный документ можно восстановить позже.
Проверьте, что получилось на самом деле
Validate судит шаблон. Рендер он судить не может — а безупречный шаблон всё равно время от времени выдаёт сломанную строку, потому что дефект живёт в сочетании выборов, а не в исходнике: два соседних слота вытянули одно слово, существительное из одного слота встретилось с местоимением из другого, неудачная склейка оставила пробел перед запятой. В шаблоне всё правильно; неправильно в одной строке из пятидесяти — а пятьдесят строк уже никто не перечитывает. Поставьте после рендера ноду Lint: бракованные строки уйдут на её второй выход, чистые поедут дальше. Направьте её не на готовую строку, а на шаблон — она сама наберёт выборку и скажет, сколько документов вышло чистыми. Это и есть число, по которому правят слоты, — до боевого запуска, а не после.
И остаётся вопрос про весь набор сразу, на который дедупликация по точному совпадению не отвечает: тексты действительно разные — или это один скелет в пятидесяти шляпах? Uniqueness читает все входящие строки как один пул, выбрасывает near-дубли и показывает футпринт — долю пятисловных окон пула, которые в нём повторяются. На реальных пулах одного объёма один шаблон даёт около 0,96, шесть шаблонов — около 0,02; контринтуитивная часть в том, что попросить больше вариантов того же шаблона бесполезно: скелет задан шаблоном, и повторный рендер ничего не разбавляет. Число сдвигают только новые шаблоны — или более плотная вариативность внутри имеющегося. И одна настройка решает, значит ли это число хоть что-нибудь: положите в Shared Strings строки, которые повторяет каждая запись (название товара, merge-тег), иначе метрика измерит название товара, а не ваш текст.
Когда текст после вас раскрывает кто-то ещё
Иногда рендер — не последний шаг: текст едет дальше в merge-теги Mailchimp, в Liquid, в макросы вашей CRM. Эти синтаксисы сталкиваются с нашим: %name% здесь переменная, а там макрос; квадратные скобки — это синтаксис перестановок, поэтому скобочный макрос выйдет без них; а косметический проход спокойно поставит пробел внутри параметра макроса. Оберните рендер в Protect Placeholders: в режиме Protect он подменяет чужие строки маркерами, которых рендер не трогает, в режиме Restore возвращает их байт-в-байт и проверяет весь круг. И громко отказывается, когда что-то не сходится, — включая ловушку, которая выглядит вообще никак: ваша переменная названа именем их макроса, наш движок раскрывает её раньше, чем тот вообще её увидит, и на руках остаётся правдоподобный, но не тот документ.
За чем не нужно следить
Спарсенные данные безопасны по умолчанию: название компании с { или | отрендерится как текст, а не как разметка — входящие значения проходят через щит движка до того, как их увидит шаблон. Переменные, которые вы набрали в ноде руками, — доверенные, так что сознательное {Mr|Ms} продолжит работать; у каждой пары есть свой переключатель щита на случай, когда в неё вставили внешние данные. И ничто здесь не звонит домой: ни ключей, ни сети, ни файловой системы — рендер происходит на вашем инстансе.
Перед боевым запуском
- Seed — выражение на строку, значит перезапуск не перетасует отправленный текст.
- Locale задана — в ноде Render, либо один раз в Build Authoring Prompt для цикла.
producedравенrequestedу Render Many — или вы знаете, почему нет.- Вы прочитали двадцать отрендеренных строк, а не одну, — или их все прочитала за вас нода Lint.
- Если сгенерирован пул, а не по строке на лида, — Uniqueness подтверждает, что он разнообразный, а не просто без дублей.
Забирайте шаблон куда угодно
Шаблон, собранный здесь, переносим по контракту: тот же синтаксис, те же вердикты валидации и та же семантика множественного числа — в движках для JavaScript, PHP, Python и Object Pascal (вся четвёрка), удерживаемых одним общим корпусом соответствия. Проверьте его в песочнице, выгрузите пакетом из Spintax Studio, отрендерьте в WordPress — актив здесь шаблон, движки взаимозаменяемы. А пока ваш сервис рассылки или e-commerce-платформа не поддержала синтаксис нативно, эта нода и есть no-code-путь к нему.
Конструкции целиком
Перестановки — про порядок и длину. Условия — про строки, зависящие от данных. Согласование с числом — про считаемые существительные. И рабочий процесс AI-в-шаблон, который авторский цикл автоматизирует.