Skip to main content

Overview

The Message Scheduler is a background service that automatically processes and sends scheduled WhatsApp messages. It runs independently from the main web application, continuously checking for pending messages and dispatching them at the scheduled time.

How the Scheduler Works

Execution Cycle

The scheduler operates on a simple but effective polling mechanism:
1

Check for Pending Messages

Every 30 seconds, the scheduler queries the database for messages that:
  • Have isSent: false
  • Have isDeleted: false
  • Have isPicked: false
  • Are scheduled for current time or up to 2 minutes ago
2

Mark Messages as Picked

To prevent duplicate processing, messages are marked with isPicked: true before sending.
3

Send Messages via WAHA

For each message, the scheduler:
  • Retrieves the associated WhatsApp session
  • Calls the WAHA API /api/sendText endpoint
  • Includes message content and target group/contact ID
4

Update Status

After sending:
  • Mark message as isSent: true
  • Record sentAt timestamp
  • Update campaign status if all messages sent
5

Handle Errors

If sending fails:
  • Log detailed error information
  • Mark campaign status as FAILED
  • Continue processing other messages

Time Window Logic

The scheduler uses a 2-minute lookback window:
const now = new Date();
const two_minutesAgo = new Date(now.getTime() - 2 * 60 * 1000);

const messagesToSend = pendingMessages.filter(message => {
    return message.scheduledAt <= now && message.scheduledAt >= two_minutesAgo;
});
This 2-minute window provides resilience against brief service interruptions while preventing very old messages from being sent unexpectedly.

Message Sending Process

When sending a message, the scheduler makes this API call:
const response = await fetch(`${process.env.WAHA_API_URL}/api/sendText`, {
    method: 'POST',
    headers: {
        'accept': 'application/json',
        'X-Api-Key': process.env.WAHA_API_KEY ?? '',
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        chatId: message.MessageCampaign?.group.groupId,
        text: message.content,
        linkPreview: true,
        linkPreviewHighQuality: false,
        session: session?.sessionName,
    })
});

Deployment Options

Deploying the scheduler on a dedicated VPS ensures 24/7 availability.
1

Create Droplet

Sign up for DigitalOcean (get $200 credit) and create a droplet:
  • OS: Ubuntu 22.04 LTS
  • Plan: Basic ($6/month or higher)
  • RAM: Minimum 1GB
  • Authentication: SSH keys (recommended) or password
2

Install Dependencies

Connect to your server and install required software:
# Connect to server
ssh root@your-server-ip

# Update system
apt update && apt upgrade -y

# Install Node.js 18+
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
apt-get install -y nodejs

# Install pnpm
npm install -g pnpm

# Install PM2 for process management
npm install -g pm2
3

Deploy Application

Clone and configure your application:
# Clone repository
git clone https://github.com/yourusername/whatsapp-group-manager.git
cd whatsapp-group-manager

# Install dependencies
pnpm install

# Create production environment file
nano .env.production
Add your environment variables:
DATABASE_URL="mongodb+srv://user:[email protected]/db"
WAHA_API_URL="http://your-waha-server:3000"
WAHA_API_KEY="your-api-key"
4

Generate Prisma Client

pnpm prisma:generate
5

Start with PM2

Launch the scheduler with PM2 for automatic restarts:
pm2 start src/scripts/messageScheduler.ts \
  --interpreter ./node_modules/.bin/tsx \
  --name whatsapp-scheduler \
  --env production

# Save PM2 configuration
pm2 save

# Setup auto-start on server reboot
pm2 startup

Option 2: Docker Container

Run the scheduler in a Docker container:
FROM node:18-alpine

WORKDIR /app

COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install

COPY . .
RUN pnpm prisma:generate

CMD ["node", "--loader", "tsx", "src/scripts/messageScheduler.ts"]
Build and run:
# Build image
docker build -t whatsapp-scheduler .

# Run container
docker run -d \
  --name scheduler \
  --env-file .env.production \
  --restart unless-stopped \
  whatsapp-scheduler

Option 3: Serverless (Advanced)

For serverless deployment, you’ll need to modify the scheduler to run as a cron job rather than a continuous process.
Serverless options may have cold start delays. Ensure your platform supports frequent execution (every 30 seconds) without excessive costs.

Monitoring the Scheduler

PM2 Commands

Manage your scheduler with these PM2 commands:
# Check status
pm2 status

# View logs (real-time)
pm2 logs whatsapp-scheduler

# View logs (last 100 lines)
pm2 logs whatsapp-scheduler --lines 100

# Monitor CPU and memory
pm2 monit

# Restart scheduler
pm2 restart whatsapp-scheduler

# Stop scheduler
pm2 stop whatsapp-scheduler

# Delete from PM2
pm2 delete whatsapp-scheduler

Log Output

The scheduler provides detailed logging:
[2026-03-04T10:00:00.000Z] No pending messages to send
[2026-03-04T10:00:30.000Z] Found 3 messages to send
Sending message to group Project Team: Daily standup reminder
Message ID: abc123, Scheduled At: 2026-03-04T10:00:00.000Z
Successfully processed message abc123

Health Monitoring

Implement health checks to ensure the scheduler is running:
# Check if process is running
pm2 list | grep whatsapp-scheduler

# Check recent logs for activity
pm2 logs whatsapp-scheduler --lines 10 --nostream

Performance Considerations

Scaling

For high-volume deployments:

Increase Check Frequency

Modify the 30-second interval for faster processing:
setInterval(() => {
  void checkAndSendScheduledMessages();
}, 15 * 1000); // Every 15 seconds

Parallel Processing

Process multiple messages simultaneously using Promise.all:
await Promise.all(
  messagesToSend.map(msg => sendMessage(msg))
);

Database Indexing

Ensure indexes on frequently queried fields:
  • scheduledAt
  • isSent
  • isDeleted
  • isPicked

Message Batching

Group messages by session to optimize API calls.

Resource Usage

Typical resource consumption:
  • CPU: < 5% (idle), up to 30% (active sending)
  • RAM: ~150-300 MB
  • Network: Minimal (depends on message volume)
  • Database Connections: 1 persistent connection

Error Handling

Automatic Recovery

The scheduler includes built-in error handling:
try {
  // Send message
} catch (error) {
  console.error(`Error processing message ${message.id}:`, error);
  
  // Mark campaign as failed
  await prisma.messageCampaign.update({
    where: { id: message.MessageCampaign.id },
    data: { status: CampaignStatus.FAILED }
  });
}
Failed messages don’t stop the scheduler. It continues processing other messages in the queue.

Common Errors

Error message:
Failed to send WhatsApp message: connect ECONNREFUSED
Causes:
  • WAHA server is down
  • Incorrect WAHA_API_URL
  • Network/firewall blocking connection
Solutions:
  1. Verify WAHA server status
  2. Check environment variables
  3. Test connectivity: curl http://waha-url/health
Error message:
PrismaClientKnownRequestError: Can't reach database server
Causes:
  • MongoDB server down
  • Network issues
  • Connection string expired
Solutions:
  1. Check MongoDB status
  2. Verify DATABASE_URL is correct
  3. Restart scheduler to reconnect
Error message:
Cannot read property 'sessionName' of null
Causes:
  • Session was deleted
  • Session disconnected
Solutions:
  1. Reconnect WhatsApp in dashboard
  2. Update campaign with valid session
  3. Delete orphaned campaigns

Graceful Shutdown

The scheduler handles termination signals:
process.on('SIGINT', () => {
  console.log('Shutting down...');
  clearInterval(interval);
  void prisma.$disconnect().then(() => process.exit(0));
});

process.on('SIGTERM', () => {
  console.log('Shutting down...');
  clearInterval(interval);
  void prisma.$disconnect().then(() => process.exit(0));
});

Deployment Checklist

1

Environment Variables

Verify all required variables are set:
  • DATABASE_URL
  • WAHA_API_URL
  • WAHA_API_KEY
2

Database Access

Test connection to MongoDB from server:
# Using mongosh
mongosh "mongodb+srv://..."
3

WAHA Connectivity

Verify WAHA server is accessible:
curl -H "X-Api-Key: your-key" http://waha-url/api/sessions
4

Prisma Client

Generate Prisma client for production:
pnpm prisma:generate
5

Start Service

Launch with PM2 and verify:
pm2 start src/scripts/messageScheduler.ts --name scheduler
pm2 save
pm2 startup
6

Monitor Logs

Watch for successful startup:
pm2 logs scheduler --lines 50
7

Test Message

Create a test campaign scheduled for 1-2 minutes in the future and verify delivery.

Best Practices

Use Process Manager

Always run the scheduler with PM2 or similar for automatic restarts and monitoring.

Monitor Regularly

Check logs daily to catch issues early and ensure messages are being processed.

Backup Strategy

Keep your server configuration and PM2 ecosystem file in version control.

Alert on Failures

Set up monitoring to alert you when the scheduler process stops.

Separate from Web App

Run scheduler on a dedicated server or container separate from your web application.

Test Thoroughly

Before production, test with various campaign types and edge cases.

Next Steps

Create Campaigns

Start scheduling messages with the scheduler running

Admin Dashboard

Monitor campaign status and system health

Build docs developers (and LLMs) love