Cara Balas WA Otomatis dengan Scheduling
Tutorial schedule pesan WhatsApp otomatis. Time-based triggers, recurring messages, campaign scheduling. Panduan lengkap!
Scheduling = Pesan tepat waktu, otomatis!
Dengan scheduling, kamu bisa jadwalkan pesan untuk dikirim di waktu tertentu - reminder, promo, atau follow up tanpa harus online.
Jenis Scheduling
📅 TIPE SCHEDULING:
1. ONE-TIME SCHEDULE
Kirim sekali di waktu tertentu
Contoh: Promo launch jam 10:00 besok
2. RECURRING SCHEDULE
Kirim berulang di pattern tertentu
Contoh: Reminder setiap Senin jam 9
3. TRIGGER-BASED DELAY
Kirim setelah X waktu dari trigger
Contoh: Follow up 1 jam setelah order
4. TIME-WINDOW BASED
Auto reply beda sesuai jam
Contoh: Greeting pagi vs malamOne-Time Schedule
Database Schema:
javascript
const scheduledMessageSchema = {
id: String,
recipient: String, // Phone number
message: String, // Message content
scheduledAt: Date, // When to send
status: String, // 'pending', 'sent', 'failed'
createdAt: Date,
sentAt: Date,
metadata: Object // Additional data
};Schedule Message:
javascript
async function scheduleMessage(recipient, message, sendAt) {
const scheduled = {
id: generateId(),
recipient,
message,
scheduledAt: new Date(sendAt),
status: 'pending',
createdAt: new Date()
};
await db.scheduledMessages.insertOne(scheduled);
return scheduled;
}
// Usage
await scheduleMessage(
'628123456789',
'Hai! Jangan lupa promo hari ini ya! 🎉',
'2026-02-18T10:00:00+07:00'
);Scheduler Worker:
javascript
const cron = require('node-cron');
// Check setiap menit untuk pesan yang perlu dikirim
cron.schedule('* * * * *', async () => {
const now = new Date();
// Get pesan yang sudah waktunya
const pendingMessages = await db.scheduledMessages.find({
status: 'pending',
scheduledAt: { $lte: now }
}).toArray();
for (const msg of pendingMessages) {
try {
await sendWhatsApp(msg.recipient, msg.message);
await db.scheduledMessages.updateOne(
{ id: msg.id },
{
$set: {
status: 'sent',
sentAt: new Date()
}
}
);
console.log(`Sent scheduled message ${msg.id}`);
} catch (error) {
await db.scheduledMessages.updateOne(
{ id: msg.id },
{
$set: {
status: 'failed',
error: error.message
}
}
);
console.error(`Failed to send ${msg.id}:`, error);
}
}
});Recurring Schedule
Recurring Schema:
javascript
const recurringScheduleSchema = {
id: String,
name: String,
recipients: [String], // Array of phone numbers or segment
message: String,
schedule: {
type: String, // 'daily', 'weekly', 'monthly', 'cron'
time: String, // '09:00'
dayOfWeek: Number, // 0-6 for weekly
dayOfMonth: Number, // 1-31 for monthly
cronExpression: String // For complex schedules
},
active: Boolean,
lastRun: Date,
nextRun: Date
};Recurring Examples:
javascript
// Daily reminder jam 9 pagi
const dailyReminder = {
id: 'daily-reminder',
name: 'Daily Morning Reminder',
recipients: ['segment:active_customers'],
message: 'Selamat pagi! ☀️ Jangan lupa cek promo hari ini!',
schedule: {
type: 'daily',
time: '09:00'
},
active: true
};
// Weekly report setiap Senin
const weeklyReport = {
id: 'weekly-report',
name: 'Weekly Report',
recipients: ['segment:vip_customers'],
message: 'Hai! Ini rangkuman belanja kamu minggu ini...',
schedule: {
type: 'weekly',
time: '10:00',
dayOfWeek: 1 // Senin
},
active: true
};
// Monthly birthday promo
const birthdayPromo = {
id: 'birthday-promo',
name: 'Birthday Promo',
recipients: ['segment:birthday_this_month'],
message: 'Happy birthday! 🎂 Ini voucher spesial untuk kamu!',
schedule: {
type: 'monthly',
time: '08:00',
dayOfMonth: 1 // Awal bulan
},
active: true
};
// Complex: Setiap weekday jam 9 dan 14
const complexSchedule = {
id: 'complex-reminder',
name: 'Weekday Reminders',
message: 'Hai! Ada yang bisa dibantu?',
schedule: {
type: 'cron',
cronExpression: '0 9,14 * * 1-5' // Mon-Fri, 9am & 2pm
},
active: true
};Recurring Processor:
javascript
// Process recurring schedules
cron.schedule('* * * * *', async () => {
const now = new Date();
const dueSchedules = await db.recurringSchedules.find({
active: true,
nextRun: { $lte: now }
}).toArray();
for (const schedule of dueSchedules) {
await processRecurringSchedule(schedule);
}
});
async function processRecurringSchedule(schedule) {
// Get recipients
const recipients = await resolveRecipients(schedule.recipients);
// Send to all
for (const recipient of recipients) {
const personalizedMessage = personalizeMessage(
schedule.message,
recipient
);
await sendWhatsApp(recipient.phone, personalizedMessage);
await sleep(100); // Rate limiting
}
// Update next run
const nextRun = calculateNextRun(schedule.schedule);
await db.recurringSchedules.updateOne(
{ id: schedule.id },
{
$set: {
lastRun: new Date(),
nextRun
}
}
);
}
function calculateNextRun(scheduleConfig) {
const now = new Date();
switch (scheduleConfig.type) {
case 'daily':
const [hours, minutes] = scheduleConfig.time.split(':');
const tomorrow = new Date(now);
tomorrow.setDate(tomorrow.getDate() + 1);
tomorrow.setHours(parseInt(hours), parseInt(minutes), 0, 0);
return tomorrow;
case 'weekly':
const nextWeek = new Date(now);
const daysUntil = (scheduleConfig.dayOfWeek - now.getDay() + 7) % 7 || 7;
nextWeek.setDate(nextWeek.getDate() + daysUntil);
const [h, m] = scheduleConfig.time.split(':');
nextWeek.setHours(parseInt(h), parseInt(m), 0, 0);
return nextWeek;
case 'cron':
const parser = require('cron-parser');
const interval = parser.parseExpression(scheduleConfig.cronExpression);
return interval.next().toDate();
default:
return null;
}
}Trigger-Based Delay
Delayed Follow Up:
javascript
// Kirim follow up X waktu setelah event
async function scheduleFollowUp(event, phone, delays) {
for (const delay of delays) {
const sendAt = new Date(Date.now() + delay.after);
await scheduleMessage(
phone,
delay.message,
sendAt
);
}
}
// Usage: Follow up sequence setelah order
const orderFollowUps = [
{
after: 1 * 60 * 60 * 1000, // 1 jam
message: 'Hai! Order kamu sedang diproses. Ada pertanyaan?'
},
{
after: 24 * 60 * 60 * 1000, // 1 hari
message: 'Update: Pesanan kamu sudah siap kirim!'
},
{
after: 3 * 24 * 60 * 60 * 1000, // 3 hari
message: 'Paket harusnya sudah sampai. Gimana produknya? 😊'
}
];
// Saat ada order baru
async function onNewOrder(order) {
await scheduleFollowUp('order', order.phone, orderFollowUps);
}Conditional Scheduling:
javascript
// Schedule dengan kondisi
async function scheduleConditional(recipient, message, sendAt, condition) {
const scheduled = {
id: generateId(),
recipient,
message,
scheduledAt: new Date(sendAt),
status: 'pending',
condition // { type: 'order_not_paid', orderId: '123' }
};
await db.scheduledMessages.insertOne(scheduled);
}
// Processor check condition sebelum kirim
async function processScheduledWithCondition(msg) {
if (msg.condition) {
const shouldSend = await checkCondition(msg.condition);
if (!shouldSend) {
await db.scheduledMessages.updateOne(
{ id: msg.id },
{ $set: { status: 'skipped', reason: 'condition_not_met' } }
);
return;
}
}
// Proceed with sending
await sendWhatsApp(msg.recipient, msg.message);
}
async function checkCondition(condition) {
switch (condition.type) {
case 'order_not_paid':
const order = await db.orders.findOne({ id: condition.orderId });
return order.status !== 'paid';
case 'no_response':
const lastMessage = await getLastMessage(condition.phone);
return lastMessage.direction === 'outgoing'; // No reply yet
default:
return true;
}
}Time-Window Auto Reply
Office Hours Response:
javascript
function getTimeBasedGreeting() {
const hour = new Date().getHours();
if (hour >= 5 && hour < 12) {
return {
greeting: 'Selamat pagi! ☀️',
availability: 'Tim kami siap membantu!'
};
}
if (hour >= 12 && hour < 15) {
return {
greeting: 'Selamat siang! 🌤️',
availability: 'Kami online sampai jam 21:00'
};
}
if (hour >= 15 && hour < 18) {
return {
greeting: 'Selamat sore! 🌅',
availability: 'Masih sempat order hari ini!'
};
}
if (hour >= 18 && hour < 21) {
return {
greeting: 'Selamat malam! 🌙',
availability: 'CS online sampai jam 21:00'
};
}
// After hours (21:00 - 05:00)
return {
greeting: 'Hai! 👋',
availability: 'CS akan balas besok pagi jam 09:00. Pesanmu sudah tercatat!'
};
}
// Auto reply dengan time-based greeting
async function handleIncomingMessage(from, message) {
const timeContext = getTimeBasedGreeting();
const response = `
${timeContext.greeting}
${timeContext.availability}
Ada yang bisa kami bantu?
1️⃣ Lihat Produk
2️⃣ Cek Pesanan
3️⃣ Bantuan
`;
await sendWhatsApp(from, response);
}Campaign Scheduling UI
Schedule Campaign:
javascript
// API endpoint untuk schedule campaign
app.post('/api/campaigns/schedule', async (req, res) => {
const { name, message, recipients, scheduledAt, recurring } = req.body;
const campaign = {
id: generateId(),
name,
message,
recipients, // Array atau segment ID
scheduledAt: new Date(scheduledAt),
recurring: recurring || null,
status: 'scheduled',
createdAt: new Date(),
stats: {
total: 0,
sent: 0,
delivered: 0,
failed: 0
}
};
await db.campaigns.insertOne(campaign);
res.json({ success: true, campaign });
});
// Get upcoming scheduled campaigns
app.get('/api/campaigns/upcoming', async (req, res) => {
const campaigns = await db.campaigns.find({
status: 'scheduled',
scheduledAt: { $gte: new Date() }
}).sort({ scheduledAt: 1 }).toArray();
res.json(campaigns);
});
// Cancel scheduled campaign
app.post('/api/campaigns/:id/cancel', async (req, res) => {
await db.campaigns.updateOne(
{ id: req.params.id, status: 'scheduled' },
{ $set: { status: 'cancelled', cancelledAt: new Date() } }
);
res.json({ success: true });
});Best Practices
DO ✅
- Schedule di waktu optimal
- Include cancel/reschedule option
- Test timezone handling
- Log semua scheduled messages
- Handle failures gracefully
- Conditional schedulingDON'T ❌
- Schedule terlalu banyak sekaligus
- Ignore timezone
- No logging
- Skip error handling
- Fixed schedule tanpa kondisi
- Schedule spamFAQ
Bagaimana handle timezone?
Simpan dalam UTC, convert ke local timezone saat display dan schedule.
Kalau server mati saat scheduled time?
Pesan akan terkirim saat server up karena check by scheduledAt <= now.
Berapa banyak bisa schedule sekaligus?
Tergantung infrastructure. Dengan queue system, ribuan tidak masalah.
Kesimpulan
Scheduling = Set it and forget it!
| Manual Sending | Scheduled |
|---|---|
| Harus online | Otomatis |
| Bisa lupa | Pasti terkirim |
| Inconsistent timing | Tepat waktu |
| Time-consuming | Efisien |