aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Discord Bots

Discord Scheduled Messages: Automate Sending with node-cron

One of the most requested features in any community bot is a Discord scheduled message system: a bot that posts a daily announcement at a fixed time, sends a weekend event reminder, or drops a "good night" message at midnight. In this guide we'll build a solid setup that sends automatic messages at a specific time using discord.js and node-cron, and we'll tackle timezones, persistent storage, and common pitfalls one by one.

The approach: why node-cron?

The first idea that comes to mind for posting at a fixed time is setTimeout or setInterval, but those work with relative intervals like "every 24 hours"; they can't express a calendar-based rule like "every day at 09:00." This is exactly where node-cron shines: it runs Linux-style cron syntax inside Node.js, so you define rules like "every day, every Monday, on the 1st of the month" in a single line.

  • Calendar-based: You specify an absolute time/day instead of calculating elapsed time yourself.
  • Timezone support: No matter what region your server runs in, you can fire the message at the correct local time.
  • Lightweight: It doesn't depend on an external service or database; it runs as long as the bot process is alive.

If your bot isn't online 24/7, any message whose schedule fires while the bot is offline is skipped. That's why running the bot on a VPS with PM2, or on uninterrupted hosting, is essential for scheduled tasks.

Installation and cron syntax

First, install the packages:

npm install discord.js node-cron

node-cron uses standard cron syntax; additionally you can prepend an optional seconds field. The order of the fields is:

# ┌──────────── second (0-59, optional)
# │ ┌────────── minute (0-59)
# │ │ ┌──────── hour (0-23)
# │ │ │ ┌────── day of month (1-31)
# │ │ │ │ ┌──── month (1-12)
# │ │ │ │ │ ┌── day of week (0-7, 0 and 7 = Sunday)
# │ │ │ │ │ │
# * * * * * *

A few practical examples:

  • 0 9 * * * → every day at 09:00
  • 30 18 * * 5 → every Friday at 18:30
  • 0 */6 * * * → every 6 hours (00:00, 06:00, 12:00, 18:00)
  • 0 0 1 * * → at midnight on the 1st of every month

You can check whether your expression is valid with cron.validate('0 9 * * *'); it returns true/false.

Sending a message at a specific time with discord.js

The core logic has two parts: getting the bot ready and starting the cron tasks in the ClientReady event. When a task fires, we fetch the target channel and send the message with send():

const { Client, GatewayIntentBits } = require('discord.js');
const cron = require('node-cron');

const client = new Client({
  intents: [GatewayIntentBits.Guilds],
});

const CHANNEL_ID = '123456789012345678';

client.once('clientReady', () => {
  console.log(`Logged in as ${client.user.tag}`);

  // Daily message at 09:00
  cron.schedule('0 9 * * *', async () => {
    try {
      const channel = await client.channels.fetch(CHANNEL_ID);
      if (channel?.isTextBased()) {
        await channel.send('Good morning! Have a great day. ☀️');
      }
    } catch (err) {
      console.error('Failed to send scheduled message:', err);
    }
  }, {
    timezone: 'Europe/Istanbul',
  });
});

client.login(process.env.DISCORD_TOKEN);

Key points:

  • client.channels.fetch() retrieves the channel from the API even if it's not cached, which makes it more reliable than cache.get().
  • The isTextBased() check confirms the channel can actually receive messages and prevents type errors.
  • A try/catch is mandatory: if the channel was deleted or the bot lost permission, the error shouldn't crash the bot.

To send messages only, GatewayIntentBits.Guilds is enough; you don't need privileged intents like MessageContent.

Timezones: the most common mistake

The thing that surprises people most with scheduled messages is the message arriving "at the wrong time." The cause is almost always the timezone. If you don't pass the timezone option, node-cron uses the server's local time. A VPS usually runs in UTC, so 0 9 * * * might land at 12:00 in Turkey.

The fix is to always set the timezone option explicitly. The value must be an IANA timezone name, such as Europe/Istanbul, Europe/Paris, or America/New_York. A nice side effect is that it handles daylight saving transitions automatically; you don't change anything, node-cron fires according to the correct local moment.

Persisting messages and dynamic scheduling

The example above hard-codes messages in the source. In a real bot you'll want admins to add new scheduled messages via a slash command. In that case you need to store the schedules in a database (SQLite, MongoDB, etc.), because the in-memory tasks are lost every time the bot restarts. The general flow looks like this:

  • An admin enters a channel, a time (cron expression) and a text via a command; the record is written to the database.
  • When the bot boots (clientReady), all records are read and a cron.schedule task is created for each.
  • You keep the tasks in a Map keyed by record id so you can cancel them later.

To stop and restart a task, you use the object returned by node-cron:

const tasks = new Map();

function scheduleMessage(record) {
  const task = cron.schedule(record.cronExpr, async () => {
    const channel = await client.channels.fetch(record.channelId);
    if (channel?.isTextBased()) await channel.send(record.text);
  }, { timezone: record.timezone });

  tasks.set(record.id, task);
}

// Cancel a schedule:
function cancelMessage(id) {
  const task = tasks.get(id);
  if (task) {
    task.stop();
    tasks.delete(id);
  }
}

With this structure you can add a new message via a "/schedule" command and stop an existing one via "/cancel". For one-off reminders, the node-schedule library, which fires at a single date/time, is a better fit than cron; for recurring messages, node-cron is ideal.

Frequently Asked Questions

What happens to messages missed while the bot is offline?

node-cron only fires while the process is running; a schedule that comes due while the bot is offline is skipped and not made up. That's why you need to keep the bot online 24/7 with PM2 or systemd for scheduled tasks. If you want to compensate for missed runs, you have to write extra logic that stores the last run time in the database and checks it on startup.

What's the difference between node-cron and node-schedule?

node-cron is designed for recurring tasks with cron syntax ("every day at 09:00"). node-schedule can fire once at a specific Date object ("once on August 17 at 14:30"), which makes it better for single reminders. Both support timezones.

Do very frequent tasks like once per second make sense?

They're possible but rarely needed. Sending messages too frequently will hit Discord's rate limits and clutter the channel. For scheduled messages, the minute or hour scale is plenty; use the seconds field only when there's a real need.

Need a reliable scheduling system for your bot? I can build a scheduled-message infrastructure with correct timezones, database backing, and resilience to restarts. Get in touch and let's plan your project together.

Bu kategorideki tüm yazılar →

Devamı için