Пошаговые инструкции Для начинающих 12 мин

Как создать ICS-файл события в n8n

Сделайте файл встречи для календаря: 10 шагов, готовый workflow, время UTC и учебный .ics. Без отправки приглашений и подключения календарного API.

n8n календарь ICS

Если после заявки нужно дать человеку файл с датой встречи, необязательно сразу подключать Google Calendar или Outlook API. В n8n можно создать .ics — файл события iCalendar. Соберём учебную встречу, проверим её время и получим файл для скачивания.

Результат инструкции

Получим meeting-demo.ics: «Учебная встреча», 20 октября 2026 года с 10:00 до 10:30 UTC, описание и место. В файле будет один VEVENT с заданным UID. Никаких писем, приглашений участникам и записей в рабочий календарь схема не отправляет.

Файл можно проверить текстовым редактором, затем при необходимости импортировать в отдельный тестовый календарь. Импорт в конкретные календарные приложения редакция не тестировала, поэтому не обещаем одинаковое поведение Google Calendar, Outlook и Apple Calendar. Проверенный результат здесь — сам файл и его содержимое.

Что понадобится

  • Отдельный новый workflow в n8n. Проверено на n8n 2.42.4, Convert to File 1.1 и Code 2.
  • Для точного повторения — учебный Docker-экземпляр с TZ=UTC и GENERIC_TIMEZONE=UTC. В импортируемой схеме timezone тоже UTC.
  • Вымышленные данные из статьи. Аккаунт календаря, SMTP и API-ключи для создания файла не нужны.

Не меняйте часовой пояс рабочего сервера ради эксперимента. Используйте отдельный учебный экземпляр. В другой среде сначала проверьте DTSTART и DTEND в файле: надпись UTC в настройке сама по себе не заменяет сверку результата.

Как установить n8n локально через Docker

Шаг 1. Импортируйте workflow

Скачать n8n-create-calendar-ics.json

Создайте новый workflow, выберите Import from File и загрузите JSON. Сохраните схему, но не публикуйте: используется ручной запуск. В ней пять узлов:

text
Manual Trigger → Sample Event → Check Event → Make ICS → Check File

Три узла Sample Event, Check Event и Check File — Code, JavaScript, Run Once for All Items. Make ICS — Convert to File. Оставьте названия как в образце: Check File обращается к Check Event по имени. Continue On Fail и Always Output Data не включайте.

Шаг 2. Задайте учебную встречу

В Sample Event вставьте:

javascript
return [{ json: {
  "title": "Учебная встреча",
  "start_utc": "2026-10-20T10:00:00.000Z",
  "end_utc": "2026-10-20T10:30:00.000Z",
  "description": "Вымышленная встреча для проверки n8n.",
  "location": "Учебный кабинет",
  "uid": "ezgpt-demo-meeting-001@example.invalid"
} }];

start_utc и end_utc — уже время UTC, а не местное время с добавленной буквой Z. Например, 10:00 UTC соответствует 13:00 при смещении UTC+3 и 15:00 при UTC+5. Присоединить Z к местному времени — значит изменить момент встречи.

UID в примере — искусственный идентификатор на example.invalid. Это не email получателя и не адрес, на который что-то будет отправлено. Для разных настоящих событий нужны разные постоянные UID; один учебный UID нельзя раздавать всем встречам.

Шаг 3. Проверьте входные поля

В Check Event вставьте код:

javascript
const items = $input.all();
if (items.length !== 1) throw new Error('one_event_required');
const e = items[0].json;
if (Object.keys(e).sort().join('|') !== 'description|end_utc|location|start_utc|title|uid') {
  throw new Error('event_fields_invalid');
}
for (const key of ['title', 'description', 'location']) {
  const max = key === 'description' ? 1200 : 80;
  if (typeof e[key] !== 'string' || !e[key].trim() || e[key].length > max ||
      !e[key].isWellFormed() || /[\u0000-\u0009\u000b-\u001f\u007f]/.test(e[key])) {
    throw new Error('event_text_invalid');
  }
  if (key !== 'description' && e[key].includes('\n')) throw new Error('single_line_required');
}
for (const key of ['start_utc', 'end_utc']) {
  const value = e[key];
  if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:00\.000Z$/.test(value) ||
      !Number.isFinite(Date.parse(value)) || new Date(value).toISOString() !== value) {
    throw new Error('canonical_utc_required');
  }
}
const minutes = (Date.parse(e.end_utc) - Date.parse(e.start_utc)) / 60000;
if (minutes <= 0 || minutes > 480) throw new Error('duration_invalid');
if (typeof e.uid !== 'string' || !/^ezgpt-demo-[a-z0-9-]{1,20}@example\.invalid$/.test(e.uid)) {
  throw new Error('demo_uid_invalid');
}
return [{ json: e }];

Проверка принимает ровно одно событие с шестью известными полями. Даты должны быть каноническими строками UTC с .000Z и точностью до минуты. Конец — позже начала, длительность — не больше восьми часов. Строка без часового пояса, неверная календарная дата или отрицательная длительность останавливают схему.

Ограничения длины текста, формата UID и длительности выбраны для учебного примера. Это не полный набор правил iCalendar. Название и место должны быть однострочными; в описании разрешён перенос строки. Экранирование для самого .ics выполняет штатный генератор, а не этот Code.

Шаг 4. Настройте создание ICS

В Make ICS выберите Convert to File → Operation: Convert to ICS. Для полей с данными включите Expression и задайте:

Поле Значение Что получится
Event Title {{ $json.title }} Название события
Start {{ $json.start_utc }} Начало в UTC
End {{ $json.end_utc }} Конец в UTC
Put Output File in Field calendar Имя поля binary с файлом

All Day оставьте выключенным. Это получасовая встреча, не событие на весь день. Поля Start и End задайте оба: не оставляйте конец на автоматическое значение. В импортированном workflow выражения уже настроены, вручную вводить их повторно не нужно.

Имя calendar обозначает поле в binary n8n, не календарь пользователя и не папку на диске. Нативная операция создаёт настоящий файл, а на выходе JSON может быть пустым — это нормально.

Шаг 5. Добавьте описание, место и UID

В Options добавьте:

Параметр Значение Назначение
File Name meeting-demo.ics Имя скачиваемого файла
Description {{ $json.description }} Описание из исходника
Location {{ $json.location }} Место из исходника
UID {{ $json.uid }} Постоянный идентификатор события

Description, Location и UID тоже используют Expression; File Name — обычное фиксированное значение. Sequence оставьте 0, Status — Confirmed. Use Workflow Timezone оставьте выключенным: в этом примере вход уже задан в UTC.

Attendees, Organizer, Recurrence Rule и напоминания не добавляйте. Этот материал создаёт файл одной встречи, не сценарий приглашений или повторов. Статус Confirmed внутри файла не означает, что кто-либо принял приглашение.

В проверенной версии при Sequence: 0 генератор не вывел отдельную строку SEQUENCE. Это наблюдаемое поведение, не потеря времени события. Само наличие UID и начальной версии не гарантирует, что повторный ручной импорт обновит событие: некоторые приложения могут создать дубль.

Шаг 6. Проверьте созданный файл

В Check File вставьте:

javascript
const items = $input.all();
if (items.length !== 1 || Object.keys(items[0].binary ?? {}).join('|') !== 'calendar') {
  throw new Error('one_calendar_file_required');
}
const item = items[0], meta = item.binary.calendar;
if (meta.fileName !== 'meeting-demo.ics' || meta.mimeType !== 'text/calendar') throw new Error('calendar_metadata_invalid');
const bytes = await this.helpers.getBinaryDataBuffer(0, 'calendar');
if (bytes.length < 1 || bytes.length > 10000) throw new Error('calendar_size_invalid');
const text = bytes.toString('utf8');
if (!Buffer.from(text, 'utf8').equals(bytes) || bytes[0] === 239) throw new Error('calendar_utf8_invalid');
const e = $('Check Event').first().json;
// Unfold content lines only for these checks, not a general iCalendar parser.
const lines = text.replace(/\r\n[ \t]/g, '').split('\r\n');
const stamp = value => value.replaceAll('-', '').replaceAll(':', '').replace('.000', '');
for (const line of ['BEGIN:VCALENDAR', 'END:VCALENDAR', 'BEGIN:VEVENT', 'END:VEVENT',
  'UID:' + e.uid, 'DTSTART:' + stamp(e.start_utc), 'DTEND:' + stamp(e.end_utc)]) {
  if (lines.filter(value => value === line).length !== 1) throw new Error('calendar_marker_mismatch');
}
if (lines.some(line => /^(ATTENDEE|ORGANIZER|RRULE)(;|:)|^BEGIN:VALARM$/.test(line))) {
  throw new Error('unexpected_calendar_action');
}
return [{ json: {
  file_name: meta.fileName, start_utc: e.start_utc, end_utc: e.end_utc,
  uid: e.uid, verified: true, action_performed: false, ics_text: text
}, binary: item.binary }];

Код читает настоящий binary.calendar через getBinaryDataBuffer, проверяет имя meeting-demo.ics, MIME text/calendar и UTF-8. Затем сверяет известные строки начала, конца и UID, наличие одного блока события и отсутствие участников, организатора, повторов и напоминаний.

Это ограниченная проверка файла, созданного нашей схемой, не универсальный iCalendar-парсер. Она не подтверждает все поля по смыслу и не заменяет проверку стороннего .ics библиотекой. Отдельно редакция разобрала успешные тестовые файлы библиотекой icalendar и сверила название, описание, место, UID и даты.

В ICS длинная строка может переноситься с продолжением на следующей строке. Код убирает такое продолжение только для своих контрольных проверок. Не редактируйте переносы и экранирование вручную: запятые, обратные слеши и переносы в описании должен записать генератор формата.

Шаг 7. Выполните всю цепочку

Нажмите Execute workflow от Manual Trigger. Должны успешно завершиться все пять узлов. В JSON последнего ожидаются следующие поля:

json
{
  "file_name": "meeting-demo.ics",
  "start_utc": "2026-10-20T10:00:00.000Z",
  "end_utc": "2026-10-20T10:30:00.000Z",
  "uid": "ezgpt-demo-meeting-001@example.invalid",
  "verified": true,
  "action_performed": false
}

Кроме них будет ics_text — полный текст файла. binary.calendar остаётся на выходе для скачивания. verified: true означает, что прошли проверки Check File; action_performed: false — что схема не отправляла приглашение и не выполняла запись в календарь.

Если Check Event остановился, Make ICS не должен создавать файл. Если Check File остановился после генерации, не используйте файл из промежуточного узла как проверенный: сначала исправьте причину и повторите полный запуск.

Шаг 8. Скачайте и откройте файл как текст

В Check File откройте вкладку Binary, выберите calendar и скачайте meeting-demo.ics. Сначала откройте его именно текстовым редактором, а не двойным щелчком: по ассоциации .ics может запуститься календарь.

Внутри ожидаются BEGIN:VCALENDAR, один BEGIN:VEVENT, затем DTSTART:20261020T100000Z и DTEND:20261020T103000Z. UID должен совпасть с Sample Event. SUMMARY содержит название, DESCRIPTION — описание, LOCATION — место. DTSTAMP отражает время создания файла, поэтому между запусками может меняться.

Скачать учебный ICS-файл

Образец получен реальным запуском схемы. Он содержит вымышленную встречу на указанную дату, не актуальное приглашение. После скачивания браузер или система могут предложить импорт — для просмотра структуры это не требуется.

Шаг 9. Сверьте время до импорта

Z в DTSTART и DTEND означает UTC. Если ваш календарь показывает местное время, отображаемые часы могут отличаться от 10:00: так и должно быть при другом часовом поясе. Сверьте именно один и тот же момент, а не одинаковые цифры на часах.

Для дополнительной проверки можно импортировать файл в отдельный пустой тестовый календарь, затем сравнить дату и получасовую длительность. Не импортируйте учебный образец в общий рабочий календарь и не рассчитывайте, что повторный импорт будет автоматически безопасен от дублей. Этот шаг зависит от вашего приложения и отдельно редакцией не проверялся.

Как привести дату из заявки к UTC в n8n

Шаг 10. Проверьте отказ на ошибочных данных

В Sample Event временно поставьте end_utc равным start_utc и запустите всю цепочку. Check Event должен остановиться с duration_invalid, файл не создаётся. Затем удалите Z из начала: ожидается canonical_utc_required. Верните исходные значения и убедитесь, что Check File снова выдаёт verified: true.

Ноль items на выходе Sample Event не означает создание пустого календаря: дальнейшие узлы не выполнятся и файла не будет. Несколько событий эта схема тоже намеренно не принимает. Для массовой выгрузки и нескольких VEVENT внутри одного файла нужен другой сценарий.

Если результат отличается

Симптом Причина Что сделать
canonical_utc_required Неверная дата или не UTC Вернуть канонический формат .000Z
duration_invalid Конец не позже начала или больше 8 часов Исправить интервал
Нет binary.calendar Неверная операция или имя поля Проверить Convert to ICS и calendar
calendar_marker_mismatch Изменились даты, UID или All Day Сверить настройки и часовой пояс
В календаре появились дубли Повторный импорт файла Проверять импорт в отдельном календаре

Перед подключением к заявкам

Сначала приведите даты из формы или CRM к UTC и определите постоянный UID для каждой встречи. Не используйте номер телефона или email как публичный идентификатор. Не добавляйте автоматическую отправку до согласования адресата, текста приглашения и правил обновления.

Создание .ics не проверяет свободные слоты, не бронирует комнату, не подтверждает участие и не синхронизируется с календарём. Для этого нужны соответствующие API и отдельные проверки. Учитывайте, что текст файла и данные встречи могут сохраняться в истории executions n8n.

Как вернуть файл из Webhook в n8n

Что проверено редакцией

18 октября 2026 года схема проверена на n8n 2.42.4 в отдельном Docker-экземпляре, процесс и workflow в UTC, binary на диске. Все 33 сценария прошли: точный публичный workflow, Unicode, спецсимволы и перенос в описании, длинное описание с переносами iCalendar, длительность 1 минута и 8 часов, переход через полночь; workflow timezone Asia/Yekaterinburg с выключенным и включённым Use Workflow Timezone в этой UTC-среде; пропуски и лишние поля, пустой и слишком длинный текст, управляющие символы и непарный суррогат, неверная дата, отсутствие зоны, смещение вместо канонического UTC, секунды, нулевая, отрицательная и слишком большая длительность, неверный UID; имя, binary-поле, изменённое время, All Day, автоматический UID и добавление участника. Два входных события отклонены, пустой источник не создал файл. Все девять успешных вариантов независимо разобраны icalendar 7.3.0: название, описание, место, UID, начало и конец совпали, нет участников, организатора, повторов и напоминаний. Sequence 0 не попал в выходной ICS, проверка это учитывает. Файл создан штатным Convert to File, не ручной склейкой. Контейнер остановлен, существующие workflows не менялись. Клики редактора, ручное скачивание из n8n, импорт в приложения, доставка приглашений, Cloud и другие часовые пояса процесса не тестировались. Обложка — AI-иллюстрация.

Частые вопросы

Нужен доступ к Google Calendar или Outlook?

Для создания .ics нет. Схема делает файл внутри n8n без календарных credentials. Запись в календарь и отправка приглашений — другие действия.

Почему в календаре время не 10:00?

В примере 10:00 UTC. Календарь может показать тот же момент в местном часовом поясе: например, 13:00 при UTC+3. Проверьте зону, дату и длительность.

Status: Confirmed означает, что участник согласился?

Нет. Это статус события в файле. В нашей схеме участников вообще нет, и никакого ответа от них не получено.

UID защитит от дублей при повторном импорте?

Гарантировать это нельзя. UID идентифицирует событие, но поведение импорта и обновления зависит от приложения и сценария. Сначала проверяйте в отдельном календаре.

Можно создать событие на весь день этой схемой?

Это другой режим. Здесь All Day выключен, проверяется точное UTC-время. Для событий на весь день нужны отдельные правила дат и окончания; простое переключение не пройдёт проверку.

Дальше по теме

Похожие материалы