Developing Triggered Email Sequences (Welcome, Abandoned Cart)
Triggered sequences are chains of emails that start automatically in response to user action. Welcome series, abandoned cart, re-engagement, onboarding tips. Each email is sent at the right moment, not on schedule.
Architecture
Typical scheme: application publishes events → worker processes and queues tasks with delay → tasks execute email sends.
User action → App Event → Queue (BullMQ) → Email Worker → ESP (Resend/SendGrid)
↓
Sequence Engine
(tracks progress,
checks conditions)
Implementation with 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 });
// Start welcome sequence
async function startWelcomeSequence(userId: string, email: string, name: string) {
const baseData = { userId, email, name };
// Email 1: immediately after registration
await emailQueue.add('welcome-step-1', baseData, {
delay: 0,
jobId: `welcome-1-${userId}`, // deduplication
});
// Email 2: after 1 day — how to use product
await emailQueue.add('welcome-step-2', baseData, {
delay: 24 * 60 * 60 * 1000,
jobId: `welcome-2-${userId}`,
});
// Email 3: after 3 days — use case examples
await emailQueue.add('welcome-step-3', baseData, {
delay: 3 * 24 * 60 * 60 * 1000,
jobId: `welcome-3-${userId}`,
});
// Email 4: after 7 days — if not activated — reminder
await emailQueue.add('welcome-step-4', baseData, {
delay: 7 * 24 * 60 * 60 * 1000,
jobId: `welcome-4-${userId}`,
});
}
Worker with Conditions
const worker = new Worker(
'email-sequences',
async (job: Job) => {
const { userId, email, name } = job.data;
// Check user hasn't unsubscribed
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':
// Send only if user hasn't created any projects
const projectCount = await db.projects.countByUser(userId);
if (projectCount > 0) return; // skip — already active
await sendEmail({
to: email,
templateId: 'welcome-04-reminder',
data: { name },
});
break;
Timeline
Implementing welcome and abandoned cart sequences — 2–3 days.







