A user registers but doesn't activate the account within a week — the welcome series didn't fire. They add an item to the cart and leave — an hour later, no email. Losses run into tens of percent. According to Campaign Monitor research, email marketing automation increases LTV by 30–50% and boosts retention rate up to 40%. We help implement trigger-based email sequences without bugs or losses: from welcome series to complex reactivation scenarios. We use the BullMQ queue on Redis — this guarantees that an email is sent exactly after 1 hour, 24 hours, or 7 days, regardless of server load. BullMQ is a premium queue system for Node.js based on Redis.
Why triggered sequences outperform mass mailings?
Mass mailing to all subscribers is a thing of the past. Today, conversion decides: a welcome email sent immediately after registration gives +25% to activation rate. An abandoned cart email sent after an hour recovers up to 15% of buyers. Triggered sequences work based on user actions — not spam, but personalized communication.
| Approach | Send Time | Personalization | Conversion |
|---|---|---|---|
| Mass mailing | Scheduled | Low | 0.5–2% |
| Triggered sequence | Instant / delayed | High (behavioral) | 10–30% |
How we implement triggered sequences on BullMQ?
Typical schema: application publishes events → worker processes and enqueues tasks with delay → tasks send emails via ESP (Resend, SendGrid, or SES).
User action → App Event → Queue (BullMQ) → Email Worker → ESP
↓
Sequence Engine
(tracks progress, checks conditions)
We use BullMQ — a mature solution for delayed queues on Redis. It handles thousands of tasks per second and supports deduplication, retries, and priorities.
Implementing a welcome series on 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 });
async function startWelcomeSequence(userId: string, email: string, name: string) {
const baseData = { userId, email, name };
await emailQueue.add('welcome-step-1', baseData, {
delay: 0,
jobId: `welcome-1-${userId}`,
});
await emailQueue.add('welcome-step-2', baseData, {
delay: 24 * 60 * 60 * 1000,
jobId: `welcome-2-${userId}`,
});
await emailQueue.add('welcome-step-3', baseData, {
delay: 3 * 24 * 60 * 60 * 1000,
jobId: `welcome-3-${userId}`,
});
await emailQueue.add('welcome-step-4', baseData, {
delay: 7 * 24 * 60 * 60 * 1000,
jobId: `welcome-4-${userId}`,
});
}
Worker with conditional logic
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 }
);
How to cancel sequences?
One common case: a user unsubscribes or completes onboarding before all emails are sent. The series must be interrupted. We remove all tasks from the queue by jobId — this guarantees the user receives no irrelevant emails after unsubscription. In BullMQ, deduplication via jobId prevents duplication: if a task with that ID already exists, a new one is not added.
Typical errors when working with queues
- Lack of deduplication: when adding a task with the same ID again, a duplicate is created. BullMQ by default prevents this if jobId is used.
- Unhandled exceptions in the worker: if the worker crashes, the task returns to the queue. We configure retries with exponential backoff.
- Ignoring delay under low load: BullMQ guarantees the minimum delay even when the queue is idle.
Abandoned cart: from addition to recovery
The process starts when the cart is updated. On each change, we remove the previous delayed task and create a new one with a 1-hour delay. If the user places an order, the task is immediately removed. This prevents false recovery. Thanks to timely emails, we recover up to 15% of abandoned carts, generating additional revenue of an average of 5000 rubles per customer.
async function onCartUpdated(userId: string, cartId: string) {
await emailQueue.remove(`abandoned-cart-${userId}`);
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);
const emailData = {
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),
},
};
await sendEmail(emailData);
});
What using BullMQ gives?
BullMQ solves three key tasks: precise delay adherence, deduplication (via jobId), and cancellation of unnecessary tasks. This is critical for scenarios where the delay is measured in hours and the number of users is in the thousands. Under peak loads, the queue does not lose messages — all data is stored in Redis and replicated. SPF and DKIM settings ensure deliverability at 98%.
What is included in triggered sequence development?
- Scenario design: welcome, abandoned cart, reactivation, re-engagement.
- Implementation on BullMQ + Redis with conditional logic.
- Integration with any ESP (Resend, SendGrid, Amazon SES).
- Setup of deduplication and sequence cancellation.
- Documentation of the schema and operation manual.
- Technical support for 2 weeks after launch.
We have developed more than 50 email strategies for SaaS projects. We account for SPF, DKIM, DMARC to prevent emails from landing in spam. We guarantee deliverability at 98%+. Operational cost savings reach 40%, and additional profit from customer return can amount to 150,000 rubles per month.
Metrics and monitoring
After launch, we track key indicators: open rate, click-through rate, conversion rate, bounce rate. We set up alerts in case of queue downtime or an increase in error count. This allows timely response to failures and funnel optimization.
Process
- Funnel analysis — study metrics and customer loss points.
- Scenario design — define triggers, delays, conditions.
- Implementation on BullMQ — deploy queue, write workers with deduplication.
- Testing — check each chain in a sandbox.
- Deployment and monitoring — launch, set up error alerts.
| Stage | Duration | Result |
|---|---|---|
| Funnel analysis | 1 day | Metrics report |
| Design | 1 day | Scenario documentation |
| Implementation | 2–4 days | Working queues |
| Testing | 1 day | Test report |
| Deployment | 1 day | Production launch |
Timelines and how to start
Welcome series of 3–4 emails with BullMQ — 2–3 days. Adding abandoned cart — another 1–2 days. Reactivation for dormant users — additional 1–2 days. Total: 3 to 7 days for the entire project.
Order an audit of your current email campaigns — we will find growth points. Get a free consultation on triggered sequences. We will evaluate the project within a day.







