#Массовая рассылка контактам
📨 Сложность: medium | Скоупы: crm, im | Стек: Node.js
#Что делаем
Создаём скрипт для персонализированной рассылки уведомлений контактам из CRM. Скрипт выбирает контакты по фильтру (например, по типу или источнику), формирует персональное сообщение с подстановкой имени и отправляет уведомление через систему сообщений Bitrix24. Это удобно для информирования клиентов о акциях, изменениях или важных событиях.
#Необходимо
- API-ключ Вайбкод с правами
crm, im - Node.js 18+
#Полный код
// mass-messaging.js
// Персонализированная рассылка уведомлений контактам CRM
const API_KEY = process.env.VIBE_API_KEY;
const BASE_URL = process.env.VIBE_BASE_URL;
const HEADERS = {
'X-Api-Key': API_KEY,
'Content-Type': 'application/json',
};
// Шаблон сообщения с плейсхолдерами
const MESSAGE_TEMPLATE = `Здравствуйте, {{NAME}}!
Рады сообщить, что мы обновили условия сотрудничества.
Новые тарифы действуют с 1 числа следующего месяца.
Подробности можно узнать у вашего менеджера или на сайте.
С уважением, команда продаж`;
// Настройки рассылки
const CONFIG = {
// Фильтр контактов
filter: {
typeId: 'CLIENT', // тип: клиент
// sourceId: 'WEB', // источник: сайт (раскомментируйте при необходимости)
},
// Задержка между сообщениями (мс) — чтобы не перегружать API
delayBetweenMessages: 1000,
// ID отправителя (пользователь Bitrix24)
senderId: 1,
// Максимум контактов за один запуск
maxContacts: 100,
};
// Загружаем контакты через Entity API
async function fetchContacts() {
const response = await fetch(`${BASE_URL}/v1/contacts/search`, {
method: 'POST',
headers: HEADERS,
body: JSON.stringify({
filter: CONFIG.filter,
select: ['id', 'name', 'lastName', 'assignedById'],
sort: { id: 'asc' },
limit: CONFIG.maxContacts,
}),
});
const { data } = await response.json();
return data || [];
}
// Подставляем данные контакта в шаблон
function personalizeMessage(template, contact) {
const fullName = [contact.name, contact.lastName].filter(Boolean).join(' ') || 'уважаемый клиент';
return template.replace(/\{\{NAME\}\}/g, fullName);
}
// Отправляем системное уведомление через Entity API (POST /v1/notifications)
async function sendNotification(contact, message) {
const response = await fetch(`${BASE_URL}/v1/notifications`, {
method: 'POST',
headers: HEADERS,
body: JSON.stringify({
userId: contact.assignedById || CONFIG.senderId,
message: `[Рассылка] Контакт: ${contact.name} ${contact.lastName}\n\n${message}`,
type: 'system',
}),
});
const result = await response.json();
if (!result.success) {
throw new Error(result.error?.message || 'Failed to send notification');
}
}
// Задержка
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
// Основная функция рассылки
async function runMailing() {
console.log('📨 Запуск рассылки');
console.log(` Фильтр: ${JSON.stringify(CONFIG.filter)}`);
console.log(` Макс. контактов: ${CONFIG.maxContacts}\n`);
// Загружаем контакты
console.log('👥 Загружаем контакты...');
const contacts = await fetchContacts();
console.log(` Найдено: ${contacts.length} контактов\n`);
if (contacts.length === 0) {
console.log('Контакты не найдены. Проверьте фильтр.');
return;
}
// Статистика
let sent = 0;
let failed = 0;
const errors = [];
// Отправляем сообщения
for (const contact of contacts) {
const fullName = `${contact.name || ''} ${contact.lastName || ''}`.trim();
try {
const message = personalizeMessage(MESSAGE_TEMPLATE, contact);
await sendNotification(contact, message);
sent++;
console.log(` ✉️ [${sent}/${contacts.length}] ${fullName} — отправлено`);
} catch (error) {
failed++;
errors.push({ contact: fullName, error: error.message });
console.log(` ❌ [${sent + failed}/${contacts.length}] ${fullName} — ошибка: ${error.message}`);
}
// Пауза между отправками
await delay(CONFIG.delayBetweenMessages);
}
// Итоги
console.log('\n' + '='.repeat(50));
console.log('📊 Итоги рассылки:');
console.log(` Отправлено: ${sent}`);
console.log(` Ошибок: ${failed}`);
console.log(` Всего: ${contacts.length}`);
if (errors.length > 0) {
console.log('\n❌ Ошибки:');
errors.forEach((e) => console.log(` - ${e.contact}: ${e.error}`));
}
console.log('='.repeat(50));
}
runMailing().catch(console.error);
#Как это работает
- Скрипт загружает список контактов из CRM через Entity API (
POST /v1/contacts/search) с заданным фильтром (тип контакта, источник и др.). - Для каждого контакта шаблон сообщения персонализируется — подставляется имя и фамилия.
- Персональное сообщение отправляется как системное уведомление ответственному менеджеру контакта через
POST /v1/notificationsс параметромtype: "system". - Между отправками выдерживается пауза, чтобы не превысить лимиты API.
- По завершении выводится статистика: сколько сообщений отправлено, сколько с ошибками.
#Что можно улучшить
- Добавить поддержку HTML-шаблонов с форматированием и ссылками
- Реализовать отправку email через Entity API (
POST /v1/activities) вместо системных уведомлений - Использовать
POST /v1/contacts/searchс пагинацией для обработки тысяч контактов - Сохранять лог рассылки в файл или базу данных для аудита