Разработка триггерных email-последовательностей (welcome, брошенная корзина)
Триггерные последовательности — это цепочки писем, которые запускаются автоматически в ответ на действие пользователя. Welcome-серия, брошенная корзина, re-engagement, onboarding-подсказки. Каждое письмо отправляется в нужный момент, а не по расписанию.
Архитектура
Типичная схема: приложение публикует события → воркер обрабатывает и ставит задачи в очередь с задержкой → задачи выполняют отправку писем.
User action → App Event → Queue (BullMQ) → Email Worker → ESP (Resend/SendGrid)
↓
Sequence Engine
(отслеживает прогресс,
проверяет условия)
Реализация на BullMQ
npm install bullmq ioredis
import { Queue, Worker, Job } from 'bullmq';
import Redis from 'ioredis';
const connection = new Redis(process.env.REDIS_URL);
const emailQueue = new Queue('email-sequences', { connection });
// Запуск welcome-последовательности
async function startWelcomeSequence(userId: string, email: string, name: string) {
const baseData = { userId, email, name };
// Письмо 1: сразу после регистрации
await emailQueue.add('welcome-step-1', baseData, {
delay: 0,
jobId: `welcome-1-${userId}`, // дедупликация
});
// Письмо 2: через 1 день — как пользоваться продуктом
await emailQueue.add('welcome-step-2', baseData, {
delay: 24 * 60 * 60 * 1000,
jobId: `welcome-2-${userId}`,
});
// Письмо 3: через 3 дня — примеры use case
await emailQueue.add('welcome-step-3', baseData, {
delay: 3 * 24 * 60 * 60 * 1000,
jobId: `welcome-3-${userId}`,
});
// Письмо 4: через 7 дней — если не активировал — напоминание
await emailQueue.add('welcome-step-4', baseData, {
delay: 7 * 24 * 60 * 60 * 1000,
jobId: `welcome-4-${userId}`,
});
}
Воркер с условиями
const worker = new Worker(
'email-sequences',
async (job: Job) => {
const { userId, email, name } = job.data;
// Проверить, не отписался ли пользователь
const user = await db.users.findById(userId);
if (!user || user.unsubscribed) return;
switch (job.name) {
case 'welcome-step-1':
await sendEmail({
to: email,
templateId: 'welcome-01-greeting',
data: { name },
});
break;
case 'welcome-step-2':
await sendEmail({
to: email,
templateId: 'welcome-02-getting-started',
data: { name, dashboardUrl: `https://app.example.com/dashboard` },
});
break;
case 'welcome-step-4':
// Отправлять только если пользователь не создал ни одного проекта
const projectCount = await db.projects.countByUser(userId);
if (projectCount > 0) return; // пропустить — он уже активный
await sendEmail({
to: email,
templateId: 'welcome-04-reminder',
data: { name },
});
break;
}
},
{ connection }
);
Брошенная корзина
// При добавлении в корзину — сохранить и поставить задачу
async function onCartUpdated(userId: string, cartId: string) {
// Отменить предыдущую задачу (если была)
await emailQueue.remove(`abandoned-cart-${userId}`);
// Новая задача через 1 час
await emailQueue.add(
'abandoned-cart',
{ userId, cartId },
{
delay: 60 * 60 * 1000,
jobId: `abandoned-cart-${userId}`,
}
);
}
// При оформлении заказа — отменить
async function onOrderPlaced(userId: string) {
await emailQueue.remove(`abandoned-cart-${userId}`);
}
// Воркер для брошенной корзины
worker.on('active', async (job) => {
if (job.name !== 'abandoned-cart') return;
const cart = await db.carts.findById(job.data.cartId);
if (!cart || cart.orderId) return; // заказ уже оформлен
const user = await db.users.findById(job.data.userId);
const cartItems = await db.cartItems.findByCart(cart.id);
await sendEmail({
to: user.email,
templateId: 'abandoned-cart',
data: {
name: user.name,
items: cartItems,
cartUrl: `https://example.com/cart?id=${cart.id}&recover=true`,
total: formatCurrency(cart.total),
},
});
});
Отмена последовательности
Важно уметь прерывать серию — при отписке, при завершении onboarding:
async function cancelWelcomeSequence(userId: string) {
await Promise.all(
[1, 2, 3, 4].map(step =>
emailQueue.remove(`welcome-${step}-${userId}`)
)
);
}
Сроки
Welcome-серия из 3–4 писем с BullMQ + условные проверки — 2–3 дня. Брошенная корзина + отмена при оформлении заказа — ещё 1–2 дня.







