How to Schedule sendDailyQuotes Cron-Job in Whiskey Socket Baileys in Node Js?

RaunaK MadanRaunaK Madan
5 min read

What is a Cron Job?

Imagine having a personal assistant who performs specific tasks at precise times—be it sending out daily reports at midnight or cleaning up logs every Sunday. That's essentially what a cron job does in the realm of computing. Originating from Unix-like operating systems, cron jobs are scheduled tasks that run at predetermined times or intervals. They automate repetitive tasks, ensuring systems run smoothly without manual intervention.

Using Node-Cron:

As a Node.js developer, you might wonder, "How can I implement such scheduling capabilities in my applications?" This is where node-cron comes into play. It's a handy npm package that allows you to schedule tasks using cron syntax directly within your Node.js environment. With node-cron, you can automate tasks like database backups, email notifications, or data cleanup without relying on external services. It's a straightforward and efficient way to introduce automation into your Node.js applications.

Why Use node-cron Over setInterval?

You might think, "Can't I just use setInterval to schedule tasks?" While setInterval can execute functions at specified intervals, it lacks the precision and flexibility of cron syntax. For instance, scheduling a task to run at 11:10 PM every day or on the last Friday of every month is cumbersome with setInterval. Node-cron provides a more readable and maintainable approach to such scheduling needs.

Install node-cron:

To start using node-cron, install it via npm:

npm i node-cron
const cron = require('node-cron');

// Schedule a task to run at 11:10 PM every day
cron.schedule('10 23 * * *', () => {
  console.log('Running a task at 11:10 PM every day');
  // Your task logic here
}, {
  scheduled: true,
  timezone: "Asia/Kolkata"
});

The cron expression '10 23 * * *' represents the schedule: minute 10, hour 23 (11 PM), every day. Each star * represents a unit in time. For more info on how to schedule a cron job refer. https://www.freecodecamp.org/news/schedule-a-job-in-node-with-nodecron/

If your server operates in a different timezone, you can specify your desired timezone. This ensures your task runs at 11:10 PM Indian Standard Time (IST), regardless of the server's local time.

Note: A good convention is to specify the timezone, as the server on which it will be deployed may have some different local time.

What is Whiskey Socket Baileys?

Whiskey Socket Baileys is a JavaScript library that allows you to integrate WhatsApp into your application using a WebSocket-based API. Unlike other solutions that rely on browser automation tools like Selenium, Baileys establishes a direct WebSocket connection with WhatsApp Web. This approach makes it more efficient and less resource-intensive, providing a smoother experience for real-time messaging without the need for heavy tools or processes.

npm i @whiskeysockets/baileys

Now for the demo, let’s schedule a cron job to send quotes daily at 11:10AM.

Demo:

Integrating Cron Jobs with Baileys:

As you want to send a daily quote to a WhatsApp group or any person(we are sending to a group) at 11:10 PM. Here's how you can achieve this by combining node-cron and Baileys:

Setup Baileys Connection: Ensure you have a stable connection to WhatsApp using Baileys. Handle connection updates to manage reconnections gracefully.

Schedule the cron-job: We scheduled the cron job which is calling a sendDailyQuote() at 11:10AM everyday.

Exception-Handling: Make sure to do proper try catch exception handling or the program may crash abruptly and check before sending the message that the socket is open.

const makeWASocket = require("@whiskeysockets/baileys").default;
const nodeCron = require("node-cron");
const axios = require("axios");  //to fetch quote from API

async function connectToWhatsApp() {
  const { state, saveCreds } = await useMultiFileAuthState("auth_info_baileys"); 
  const sock = makeWASocket({
    printQRInTerminal: true,
    auth: state,
    keepAliveIntervalMs: 20000,
    defaultQueryTimeoutMs: 0,
  });

  sock.ev.on("connection.update", (update) =>
    handleConnectionUpdate(update, sock)
  );
  sock.ev.on("creds.update", saveCreds);

  //handle incoming messages
  sock.ev.on("messages.upsert", async (messageUpdate) =>
    handleMessagesUpsert(messageUpdate, sock)
  );

  console.log("Connected to WhatsApp");
}

function handleConnectionUpdate(update, sock) {
  const { connection, lastDisconnect, qr } = update || {};
  if (qr) {
    console.log(qr);
  }
  if (connection === "close") {
    const shouldReconnect =
      lastDisconnect?.error?.output?.statusCode !== DisconnectReason.loggedOut;

    if (shouldReconnect) {
      connectToWhatsApp();
    }
  } else if (connection == "open") {
    console.log("opened connection");
    try {
        nodeCron.schedule("10 11 * * *",  async () => {
        console.log("Cron job triggered for quotes!");
        // Check if socket is open before sending a message
        if (sock.ws?.readyState === sock.ws.OPEN) {
          try {
            await sendDailyQuote(sock, process.env.CODE_ON_REMOTEJ_ID);
          } catch (sendError) {
            console.error("Error sending message:", sendError);
          }
        } else {
          console.error("Socket is not open. Unable to send message.");
        }
      }, {
        scheduled: true,
        timezone: "Asia/Kolkata"
      });
      console.log("Cron job scheduled successfully!");
    } catch (error) {
      console.error("Error during cron job setup:", error);
    }
  }
}
connectToWhatsApp();

Implement SendDailyQuote:

async function sendDailyQuote(sock, jid) {
  try {
    const quote = await fetchQuotes();
    if (sock.ws?.readyState !== sock.ws.OPEN) {
      console.error("Socket is not open. Retrying in 5 seconds...");
      await new Promise((resolve) => setTimeout(resolve, 5000));
      if (sock.ws?.readyState !== sock.ws.OPEN) {
        console.error("Socket still not open. Skipping message send.");
        return;
      }
    }
    await sock.sendMessage(jid, { text: quote });
    console.log("Quote sent successfully!");
  } catch (error) {
    console.error("Error sending daily quote:", error);
    // Optionally, add retry logic here if needed 
  }
};

//call the api
async function fetchQuotes() {
    try {
        const res = await axios.get("https://zenquotes.io/api/today");
        const quote = res.data[0];
        const message = `"`  + quote.q + `"` +  " — " + quote.a;
        console.log("Fetching quote:" + `"`  + quote.q + `"` +  " — " + quote.a);
        return message;
    } catch (e) {
        console.log("Error fetching quote", e);
        return "Unable to fetch quote.";
    }
}

Questions you may have?

Q1. Why call the cron job in connection update event listener and not anywhere else?

When scheduling a cron job, timing and dependency matter. The reason we place it inside the connection update listener rather than message upsert or any other event is simple:

  1. Connection Stability – We schedule the cron job only after the connection is successfully established. If the connection isn’t stable, scheduling tasks prematurely might lead to unexpected failures.

  2. Independence from Messages – Unlike message upsert, which triggers on every incoming message, the cron job for scheduling news is not message-dependent. It doesn’t need to wait for a new message to arrive; it simply runs at a fixed scheduled time.

NOTE: Add default query timeout to 0 in the config to prevent query timeout.

defaultQueryTimeoutMs: 0, When executing database queries, there's typically a default timeout after which the query will be forcefully terminated if it takes too long to execute. This can lead to unexpected failures.

Thank you for reading.

0
Subscribe to my newsletter

Read articles from RaunaK Madan directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

RaunaK Madan
RaunaK Madan

Open source enthusiast who loves to build cool stuff.