Scheduled announcements and reminders

A bot that posts a daily good-morning, a weekly reminder, or clears a channel on a timer — built with node-cron inside your bot, and where the panel's own Schedules page fits instead.

Plenty of bots need to act on a clock: a daily announcement, a weekly event reminder, a nightly cleanup. The tool is node-cron — it runs a function of yours on a schedule you write as a cron expression, right inside your bot. This recipe schedules a daily post and shows how to read cron expressions.

At a glance
You need a working bot from the discord.js guide, and the channel ID you want to post in
Plan free or premium — but a timed bot only fires while it's online (see below)
Time about fifteen minutes

Install node-cron from the Packages page (search node-cron, install, restart), then require it next to discord.js.

Schedule a daily post

Set the schedule up in ClientReady — after login, when the bot can actually reach channels:

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

const ANNOUNCE_CHANNEL_ID = process.env.ANNOUNCE_CHANNEL_ID; // put the ID in .env

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

client.once(Events.ClientReady, (c) => {
  console.log(`Listening as ${c.user.tag}`);

  // Every day at 09:00 in the given timezone
  cron.schedule('0 9 * * *', async () => {
    const channel = await client.channels.fetch(ANNOUNCE_CHANNEL_ID);
    if (channel?.isTextBased()) {
      await channel.send('☀️ Good morning! Server is up and running.');
    }
  }, { timezone: 'Europe/London' });
});

client.login(process.env.DISCORD_TOKEN);

The pieces:

  • cron.schedule(expr, fn, options) runs fn every time the clock matches expr.
  • client.channels.fetch(id) looks the channel up by ID; .send() posts to it. The isTextBased() guard skips voice/category channels that can't receive a message.
  • { timezone: '...' } pins the schedule to a real timezone (an IANA name like America/New_York or Europe/London). Without it, "09:00" follows the server's own clock — set it so 9 AM means 9 AM where you are.

💡 Tip: Get a channel's ID by enabling Developer Mode in Discord (Settings → Advanced), then right-click the channel → Copy Channel ID. Keep it in .env alongside your token so it's not hardcoded.

Reading cron expressions

A cron expression is five fields: minute, hour, day-of-month, month, day-of-week. A * means "every". node-cron also accepts an optional sixth field at the front for seconds.

Expression Runs
0 9 * * * every day at 09:00
*/30 * * * * every 30 minutes
0 * * * * at the top of every hour
0 20 * * 5 every Friday at 20:00
0 0 1 * * midnight on the 1st of each month
*/10 * * * * * every 10 seconds (six-field form)

Check an expression before trusting it with cron.validate('0 9 * * *'), which returns true for a valid one. When in doubt, an online "crontab" helper spells any expression out in plain English.

One-off reminders

For a /remindme in 10 minutes — a single future action, not a repeating one — you don't need cron at all. setTimeout fires once:

setTimeout(() => {
  interaction.followUp(`⏰ <@${interaction.user.id}>, here's your reminder!`);
}, 10 * 60 * 1000); // 10 minutes

⚠️ Heads up: A setTimeout (and any schedule) lives only in memory. If the bot restarts before it fires, it's gone. For reminders that must survive a restart, write them to a database with their due time and re-load pending ones on startup — see the persistent reminder recipe.

node-cron or the panel's Schedules page?

Falix has its own Schedules page, and the two solve different halves:

Use... for...
node-cron (in your bot) actions inside Discord — post an embed, DM a user, run bot logic
Panel Schedules actions on the server — restart the bot nightly, take a backup, run a console command

They pair well: a panel schedule restarts your bot at 5 AM to clear memory, while node-cron handles the 9 AM announcement.

🎯 Good to know: node-cron only fires while the bot process is running. On a free plan the bot stops when the session timer expires, and scheduled posts stop with it. For reliable daily posts, keep the bot online — realistically a premium plan that runs 24/7. See Keeping apps online.

Verify it works

Set a temporary fast schedule — */1 * * * * (every minute) posting to a test channel — start the bot, and watch a message land at the top of the next minute. Once you've seen it fire, switch back to the real 0 9 * * *.

Troubleshooting

  • Nothing ever posts — check the console at startup for a cron error, confirm the bot is online, and validate the expression with cron.validate(...).
  • Unknown Channel or a fetch error — the channel ID is wrong, or the bot isn't in that server / can't see the channel. Copy the ID again and check the bot's channel permissions.
  • It fires at the wrong time — you didn't set timezone, so it followed the server clock. Pass an IANA timezone name.
  • The post stopped happening — the bot went offline (free-plan timer or a crash). node-cron can't fire while the process is down; keep the bot online.

Next steps

Was this guide helpful?