Een van de meest gevraagde functies in een community-bot is een systeem voor Discord geplande berichten: een bot die elke dag op een vast tijdstip een aankondiging plaatst, een evenementherinnering in het weekend stuurt of om middernacht een "welterusten"-bericht post. In deze gids bouwen we een stevige opzet die automatische berichten op een specifiek tijdstip verstuurt met discord.js en node-cron, en behandelen we tijdzones, persistente opslag en veelgemaakte fouten één voor één.
De aanpak: waarom node-cron?
Het eerste idee om op een vast tijdstip te posten is setTimeout of setInterval, maar die werken met relatieve intervallen zoals "elke 24 uur"; ze kunnen geen kalendergebaseerde regel zoals "elke dag om 09:00" uitdrukken. Precies hier blinkt node-cron uit: het draait Linux-cron-syntaxis binnen Node.js, dus je definieert regels als "elke dag, elke maandag, op de 1e van de maand" in één regel.
- Kalendergebaseerd: je geeft een absoluut tijdstip/dag op in plaats van zelf de verstreken tijd te berekenen.
- Tijdzone-ondersteuning: ongeacht in welke regio je server draait, kun je het bericht op het juiste lokale tijdstip laten afgaan.
- Lichtgewicht: het is niet afhankelijk van een externe dienst of database; het draait zolang het bot-proces actief is.
Als je bot niet 24/7 online is, wordt elk bericht waarvan de planning afgaat terwijl de bot offline is, overgeslagen. Daarom is de bot draaien op een VPS met PM2, of op ononderbroken hosting, essentieel voor geplande taken.
Installatie en cron-syntaxis
Installeer eerst de pakketten:
npm install discord.js node-cron
node-cron gebruikt de standaard cron-syntaxis; daarnaast kun je vooraan een optioneel seconden-veld toevoegen. De volgorde van de velden is:
# ┌──────────── seconde (0-59, optioneel)
# │ ┌────────── minuut (0-59)
# │ │ ┌──────── uur (0-23)
# │ │ │ ┌────── dag van de maand (1-31)
# │ │ │ │ ┌──── maand (1-12)
# │ │ │ │ │ ┌── dag van de week (0-7, 0 en 7 = zondag)
# │ │ │ │ │ │
# * * * * * *
Een paar praktische voorbeelden:
0 9 * * *→ elke dag om 09:0030 18 * * 5→ elke vrijdag om 18:300 */6 * * *→ elke 6 uur (00:00, 06:00, 12:00, 18:00)0 0 1 * *→ om middernacht op de 1e van elke maand
Je kunt controleren of je expressie geldig is met cron.validate('0 9 * * *'); die retourneert true/false.
Een bericht op een specifiek tijdstip versturen met discord.js
De kernlogica bestaat uit twee delen: de bot gereedmaken en de cron-taken starten in het ClientReady-event. Wanneer een taak afgaat, halen we het doelkanaal op en versturen we het bericht met 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(`Ingelogd als ${client.user.tag}`);
// Dagelijks bericht om 09:00
cron.schedule('0 9 * * *', async () => {
try {
const channel = await client.channels.fetch(CHANNEL_ID);
if (channel?.isTextBased()) {
await channel.send('Goedemorgen! Maak er een mooie dag van. ☀️');
}
} catch (err) {
console.error('Gepland bericht kon niet worden verzonden:', err);
}
}, {
timezone: 'Europe/Amsterdam',
});
});
client.login(process.env.DISCORD_TOKEN);
Belangrijke punten:
client.channels.fetch()haalt het kanaal op uit de API, ook als het niet in de cache staat, wat het betrouwbaarder maakt dancache.get().- De
isTextBased()-controle bevestigt dat het kanaal daadwerkelijk berichten kan ontvangen en voorkomt typefouten. - Een
try/catchis verplicht: als het kanaal is verwijderd of de bot zijn rechten kwijt is, mag de fout de bot niet laten crashen.
Om alleen berichten te versturen is GatewayIntentBits.Guilds genoeg; je hebt geen geprivilegieerde intents zoals MessageContent nodig.
Tijdzones: de meest gemaakte fout
Wat mensen het meest verrast bij geplande berichten, is dat het bericht "op het verkeerde tijdstip" aankomt. De oorzaak is bijna altijd de tijdzone. Als je de timezone-optie niet meegeeft, gebruikt node-cron de lokale tijd van de server. Een VPS draait meestal in UTC, dus 0 9 * * * kan in Nederland op 10:00 of 11:00 uitkomen.
De oplossing is om de timezone-optie altijd expliciet in te stellen. De waarde moet een IANA-tijdzonenaam zijn, zoals Europe/Amsterdam, Europe/Paris of America/New_York. Een prettig neveneffect: het verwerkt zomer-/wintertijdovergangen automatisch; je verandert niets, node-cron gaat af op het juiste lokale moment.
Berichten persistent opslaan en dynamisch plannen
Het bovenstaande voorbeeld zet berichten hard in de broncode. In een echte bot wil je dat beheerders nieuwe geplande berichten toevoegen via een slash-commando. In dat geval moet je de planningen in een database (SQLite, MongoDB, enz.) opslaan, want de taken in het geheugen gaan bij elke herstart van de bot verloren. De algemene flow ziet er zo uit:
- Een beheerder voert via een commando een kanaal, een tijd (cron-expressie) en een tekst in; het record wordt naar de database geschreven.
- Bij het opstarten van de bot (
clientReady) worden alle records gelezen en wordt voor elk eencron.schedule-taak aangemaakt. - Je bewaart de taken in een
Mapmet de record-id als sleutel, zodat je ze later kunt annuleren.
Om een taak te stoppen en te herstarten gebruik je het object dat node-cron retourneert:
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);
}
// Een planning annuleren:
function cancelMessage(id) {
const task = tasks.get(id);
if (task) {
task.stop();
tasks.delete(id);
}
}
Met deze structuur kun je een nieuw bericht toevoegen via een "/plan"-commando en een bestaande planning stoppen via "/annuleer". Voor eenmalige herinneringen past de node-schedule-bibliotheek, die op één datum/tijd afgaat, beter dan cron; voor terugkerende berichten is node-cron ideaal.
Veelgestelde vragen
Wat gebeurt er met berichten die gemist worden terwijl de bot offline is?
node-cron gaat alleen af terwijl het proces draait; een planning die afloopt terwijl de bot offline is, wordt overgeslagen en niet ingehaald. Daarom moet je de bot 24/7 online houden met PM2 of systemd voor geplande taken. Wil je gemiste runs compenseren, dan moet je extra logica schrijven die de laatste uitvoeringstijd in de database opslaat en bij het opstarten controleert.
Wat is het verschil tussen node-cron en node-schedule?
node-cron is ontworpen voor terugkerende taken met cron-syntaxis ("elke dag om 09:00"). node-schedule kan één keer afgaan op een specifiek Date-object ("eenmalig op 17 augustus om 14:30"), wat het beter maakt voor losse herinneringen. Beide ondersteunen tijdzones.
Zijn heel frequente taken zoals één keer per seconde zinvol?
Ze zijn mogelijk maar zelden nodig. Te vaak berichten versturen botst tegen de rate limits van Discord en vervuilt het kanaal. Voor geplande berichten is de schaal van minuten of uren ruim voldoende; gebruik het secondenveld alleen als er een echte noodzaak is.
Heb je een betrouwbaar planningssysteem voor je bot nodig? Ik kan een infrastructuur voor geplande berichten bouwen met correcte tijdzones, database-ondersteuning en bestand tegen herstarts. Neem contact op en laten we je project samen plannen.