---
title: "Discord reminder command: a bot that DMs you after 10m, 2h or 3d | StreetHosting"
description: "Build a /lembrete reminder command in discord.js v14 with 10m, 2h or 3d times, saved to a database to survive restarts, sent by DM with a channel fallback."
url: "https://streethosting.com.br/en/guides/discord-bots/discord-reminder-bot"
type: "page"
language: "en-US"
---

Discord Bots · 7 min · Intermediate

Published on Nov 13, 2026 · Updated on Nov 13, 2026

# Reminder bot for Discord: a /lembrete command with time, DM and a per-user limit

A reminder is only worth anything if it arrives even after the bot restarts. Learn the duration parser, the table that stores the reminders, DM delivery with a channel fallback and the limit that prevents abuse.

By [Equipe StreetHosting](https://streethosting.com.br/en/autores#equipe-streethosting) · StreetHosting infrastructure and support team

[Automation and webhooks](https://streethosting.com.br/en/guides/topics/automation)

Summarize with:

[](https://chat.openai.com/?q=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fdiscord-bots%2Fdiscord-reminder-bot.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "ChatGPT") [](https://claude.ai/new?q=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fdiscord-bots%2Fdiscord-reminder-bot.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "Claude") [](https://www.google.com/search?udm=50&aep=11&q=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fdiscord-bots%2Fdiscord-reminder-bot.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "Google AI Mode") [](https://x.com/i/grok?text=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fdiscord-bots%2Fdiscord-reminder-bot.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "Grok") [](https://www.perplexity.ai/search/new?q=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fdiscord-bots%2Fdiscord-reminder-bot.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "Perplexity")

Share:

[](https://x.com/intent/tweet?text=Discord%20reminder%20command%3A%20a%20bot%20that%20DMs%20you%20after%2010m%2C%202h%20or%203d&url=https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fdiscord-bots%2Fdiscord-reminder-bot "Share on X") [](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fdiscord-bots%2Fdiscord-reminder-bot "Share on Facebook") [](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fdiscord-bots%2Fdiscord-reminder-bot "Share on LinkedIn") [](https://wa.me/?text=Discord%20reminder%20command%3A%20a%20bot%20that%20DMs%20you%20after%2010m%2C%202h%20or%203d%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fdiscord-bots%2Fdiscord-reminder-bot "Share on WhatsApp")

For agents: Copy as Markdown [.md](https://streethosting.com.br/en/guides/discord-bots/discord-reminder-bot.md)

In this guide 7 sections

* [How a reminder works inside](https://streethosting.com.br/en/guides/discord-bots/discord-reminder-bot#como-funciona)
* [Duration parser: 10m, 2h, 3d](https://streethosting.com.br/en/guides/discord-bots/discord-reminder-bot#parser-duracao)
* [The table and the /lembrete command](https://streethosting.com.br/en/guides/discord-bots/discord-reminder-bot#tabela-comando)
* [Deliver by DM with a channel fallback](https://streethosting.com.br/en/guides/discord-bots/discord-reminder-bot#entrega)
* [Variations worth adding](https://streethosting.com.br/en/guides/discord-bots/discord-reminder-bot#variacoes)
* [Limits to prevent abuse](https://streethosting.com.br/en/guides/discord-bots/discord-reminder-bot#limites)
* [Where to run this on StreetHosting](https://streethosting.com.br/en/guides/discord-bots/discord-reminder-bot#onde-rodar)

Quick answer

Convert the user's text ("2h", "3d") into milliseconds with a parser, store the due date, the user, the channel and the message in the database, and run a sweep every 30 seconds that delivers what has expired. Send it by DM and, if DMs are closed, post it in the original channel. Because the deadline is in the database, the reminder survives any restart. Limit pending reminders per user to prevent abuse.

## How a reminder works inside[](https://streethosting.com.br/en/guides/discord-bots/discord-reminder-bot#como-funciona)

The design is the same as any scheduled task in a bot: separate what was requested from the moment of running it. The command only writes a record with the date the notice should go out. A short routine, running every 30 seconds, queries what has expired, delivers it and deletes the record.

This design exists because the naive alternative, one `setTimeout`per reminder, has two fatal flaws: the timer lives in memory and vanishes when the bot restarts, and Node's limit is 2147483647 milliseconds, about 24.8 days. The guide on [temporary roles](https://streethosting.com.br/en/guides/discord-bots/discord-temporary-roles-bot) explains those two problems in detail, and the solution here follows the same logic.

## Duration parser: 10m, 2h, 3d[](https://streethosting.com.br/en/guides/discord-bots/discord-reminder-bot#parser-duracao)

The user types "10m", "2h", "3d" or even "1h30m". The parser has to accept that format and reject everything else, because a text like "10 banana" cannot become a reminder for 10 of anything:

`// duracao.js const UNIDADES = { s: 1000, m: 60 * 1000, h: 60 * 60 * 1000, d: 24 * 60 * 60 * 1000, w: 7 * 24 * 60 * 60 * 1000, }; const MINIMO = 60 * 1000; // 1 minute const MAXIMO = 365 * UNIDADES.d; function parseDuracao(texto) { const limpo = texto.toLowerCase().replace(/\s+/g, ''); if (!/^(\d+[smhdw])+$/.test(limpo)) return null; let total = 0; for (const [, valor, unidade] of limpo.matchAll(/(\d+)([smhdw])/g)) { total += Number(valor) * UNIDADES[unidade]; } if (!Number.isFinite(total) || total < MINIMO || total > MAXIMO) return null; return total; } module.exports = { parseDuracao };`

The first regular expression guarantees that the whole string is a sequence of number plus unit, with no junk in the middle. The second walks through each pair and adds them up. The result is checked against the minimum and the maximum, so "0m", "99999d" and "abc" return `null`, and the command answers that the format is invalid.

## The table and the /lembrete command[](https://streethosting.com.br/en/guides/discord-bots/discord-reminder-bot#tabela-comando)

With SQLite through `better-sqlite3` (`npm install better-sqlite3`), the table stores who asked, where they asked, the text and the due date. If your bot already uses MariaDB, the structure is the same, with `BIGINT` in place of the integer, `AUTO_INCREMENT` and `VARCHAR(20)` for the IDs, because MariaDB does not index a TEXT column without a length:

`` // lembretes.js const Database = require('better-sqlite3'); const db = new Database('dados.db'); db.pragma('journal_mode = WAL'); db.exec(` CREATE TABLE IF NOT EXISTS lembretes ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL, guild_id TEXT, channel_id TEXT NOT NULL, texto TEXT NOT NULL, criado_em INTEGER NOT NULL, vence_em INTEGER NOT NULL ); CREATE INDEX IF NOT EXISTS idx_vence_em ON lembretes (vence_em); CREATE INDEX IF NOT EXISTS idx_user ON lembretes (user_id); `); const LIMITE_POR_USUARIO = 10; const contarDoUsuario = db.prepare('SELECT COUNT(*) AS n FROM lembretes WHERE user_id = ?'); const inserir = db.prepare( 'INSERT INTO lembretes (user_id, guild_id, channel_id, texto, criado_em, vence_em) VALUES (?, ?, ?, ?, ?, ?)' ); const listar = db.prepare('SELECT id, texto, vence_em FROM lembretes WHERE user_id = ? ORDER BY vence_em'); const cancelar = db.prepare('DELETE FROM lembretes WHERE id = ? AND user_id = ?'); const vencidos = db.prepare('SELECT * FROM lembretes WHERE vence_em <= ? ORDER BY vence_em LIMIT 100'); const apagar = db.prepare('DELETE FROM lembretes WHERE id = ?'); module.exports = { db, LIMITE_POR_USUARIO, contarDoUsuario, inserir, listar, cancelar, vencidos, apagar }; ``

Now the command. We use subcommands: **criar** (create), **listar** (list) and **cancelar** (cancel). Registration and interaction handling are covered in the guide on [slash commands](https://streethosting.com.br/en/guides/discord-bots/create-discord-bot-slash-commands).

`` const { SlashCommandBuilder, MessageFlags } = require('discord.js'); const { parseDuracao } = require('./duracao'); const L = require('./lembretes'); const lembrete = new SlashCommandBuilder() .setName('lembrete') .setDescription('Lembretes pessoais') .addSubcommand((s) => s.setName('criar').setDescription('Cria um lembrete') .addStringOption((o) => o.setName('tempo').setDescription('Ex.: 10m, 2h, 3d, 1h30m').setRequired(true)) .addStringOption((o) => o.setName('texto').setDescription('O que lembrar').setMaxLength(500).setRequired(true)) ) .addSubcommand((s) => s.setName('listar').setDescription('Mostra seus lembretes')) .addSubcommand((s) => s.setName('cancelar').setDescription('Cancela um lembrete') .addIntegerOption((o) => o.setName('id').setDescription('ID mostrado em /lembrete listar').setRequired(true)) ); // in the interactionCreate handler if (interaction.commandName === 'lembrete') { const sub = interaction.options.getSubcommand(); const efemero = { flags: MessageFlags.Ephemeral }; if (sub === 'criar') { const duracao = parseDuracao(interaction.options.getString('tempo', true)); if (!duracao) { return interaction.reply({ content: 'Tempo inválido. Use algo como 10m, 2h, 3d ou 1h30m (de 1 minuto a 365 dias).', ...efemero }); } if (L.contarDoUsuario.get(interaction.user.id).n >= L.LIMITE_POR_USUARIO) { return interaction.reply({ content: `Você já tem ${L.LIMITE_POR_USUARIO} lembretes pendentes. Cancele algum antes.`, ...efemero }); } const agora = Date.now(); const venceEm = agora + duracao; L.inserir.run( interaction.user.id, interaction.guildId, interaction.channelId, interaction.options.getString('texto', true), agora, venceEm ); return interaction.reply({ content: `Combinado. Aviso você <t:${Math.floor(venceEm / 1000)}:R>.`, ...efemero }); } if (sub === 'listar') { const linhas = L.listar.all(interaction.user.id).map( (l) => `#${l.id} <t:${Math.floor(l.vence_em / 1000)}:R>: ${l.texto.slice(0, 80)}` ); return interaction.reply({ content: linhas.join('\n') || 'Você não tem lembretes pendentes.', ...efemero }); } if (sub === 'cancelar') { const resultado = L.cancelar.run(interaction.options.getInteger('id', true), interaction.user.id); return interaction.reply({ content: resultado.changes ? 'Lembrete cancelado.' : 'Não achei esse lembrete na sua lista.', ...efemero, }); } } ``

Two careful choices here. The replies are ephemeral, so only the person sees what they scheduled. And `cancelar` filters by `user_id`, which stops anyone from cancelling another person's reminder just by guessing the number.

## Deliver by DM with a channel fallback[](https://streethosting.com.br/en/guides/discord-bots/discord-reminder-bot#entrega)

The sweep fetches the expired ones, tries the DM and, if it fails, uses the channel where the command was given. Error 50007 almost always means the person closed server member DMs, as the guide on [automatic DMs](https://streethosting.com.br/en/guides/discord-bots/discord-bot-send-automatic-dm) explains:

`` // entrega.js const L = require('./lembretes'); let rodando = false; async function entregar(client, item) { const texto = `Lembrete: ${item.texto}`; try { const usuario = await client.users.fetch(item.user_id); await usuario.send({ content: texto, allowedMentions: { parse: [] } }); return true; } catch (err) { if (err.code !== 50007) { console.error('Erro inesperado ao enviar DM de lembrete:', err); } } // DM closed: try the original channel, mentioning only whoever asked try { const canal = await client.channels.fetch(item.channel_id); await canal.send({ content: `<@${item.user_id}> ${texto}`, allowedMentions: { users: [item.user_id] }, }); return true; } catch (err) { // 10003 = channel deleted, 50001/50013 = no access or no permission if ([10003, 50001, 50013].includes(err.code)) return true; // no way to deliver, discard console.error('Erro ao enviar lembrete no canal:', err); return false; // temporary error, try again next round } } async function varrer(client) { if (rodando) return; rodando = true; try { for (const item of L.vencidos.all(Date.now())) { if (await entregar(client, item)) L.apagar.run(item.id); } } finally { rodando = false; } } function iniciarLembretes(client) { varrer(client); // catches up on what expired while the bot was down setInterval(() => varrer(client), 30 * 1000); } module.exports = { iniciarLembretes }; ``

Call `iniciarLembretes(client)` inside `Events.ClientReady`. The golden rule is to delete the row only when delivery worked, or when it is certain it never will. A network error leaves the reminder in the database, and it goes out in the next round. If the bot was offline for an hour, all the late notices go out at once when it returns, which beats losing them all. For a large burst, the `LIMIT 100` spreads delivery across rounds and avoids the [429 rate limit](https://streethosting.com.br/en/guides/discord-bots/fix-discord-429-rate-limit).

With a sweep every 30 seconds, a "1m" reminder arrives between 1 and 1.5 minutes later. For most uses that is invisible. If you need more exactness, compute the next due date and schedule a short `setTimeout` (never above 24 days) instead of a fixed interval.

## Variations worth adding[](https://streethosting.com.br/en/guides/discord-bots/discord-reminder-bot#variacoes)

With the base ready, a few improvements cost only a few lines and make the command much more useful.

* **A link to the conversation.** Also store the message or interaction ID and build the link `https://discord.com/channels/<servidor>/<canal>/<mensagem>`. In the reminder, the person clicks and goes back to the context where they asked for the notice.
* **Repeating reminder.** Add a column with the repeat interval. On delivery, instead of deleting, add the interval to the due date and keep the row. Set a maximum number of repetitions, otherwise a forgotten reminder goes on forever.
* **A set time instead of a duration.**"Tomorrow at 6 pm" depends on the person's time zone, and Discord does not tell the bot that zone. If you want that format, ask the user to register the time zone once and convert the time when saving. For Brazil, keep in mind that Brasília time is the one that applies for most people.
* **Snooze.**When delivering by DM, attach a "Remind me again in 10 minutes" button. The click creates a new record with the same text.

Each of these ideas touches only the table and the delivery routine. The parser, the command and the sweep stay as they are, which is the benefit of having separated the request from the execution.

## Limits to prevent abuse[](https://streethosting.com.br/en/guides/discord-bots/discord-reminder-bot#limites)

A command that schedules messages and sends them by DM is a natural target for abuse. The limits below cost little and close the most common gaps:

* **Maximum pending per user.** The example uses 10. People who need more are rare, and anyone trying to fill the queue is stopped early.
* **Minimum interval of 1 minute.** Without a minimum, someone schedules reminders of a few seconds and turns the bot into a DM generator.
* **Limited text.**500 characters fit any note and keep the message well under Discord's 2000 limit, even with the prefix.
* **No broad mentions.** The `allowedMentions` in the code stops a text containing @everyone or a role from notifying the whole server.
* **Cooldown on the command.** If your server is large, a gap of a few seconds between uses by the same user reduces spam.

Never trust user text as code or as a query. The examples use prepared SQLite statements with parameters, which prevent SQL injection. Do not build the query by joining strings.

The reminders live in your database, so back up the file. A daily copy of `dados.db` already avoids losing pending notices to a disk failure. If you want fixed messages at a set time for the whole server, see [scheduling messages on Discord](https://streethosting.com.br/en/guides/discord-bots/discord-bot-schedule-messages).

## Where to run this on StreetHosting[](https://streethosting.com.br/en/guides/discord-bots/discord-reminder-bot#onde-rodar)

A reminder bot only works if it is on when the deadline arrives, which means 24 hours a day. On [StreetHosting bot hosting](https://streethosting.com.br/en/bots) your Node.js code runs in a container with a panel, and the Discord Essential plan, at R$ 9.99 per month, brings 1 GB of RAM, 2 GB of NVMe and a MariaDB database, more than enough for this bot. The SQLite file lives on the container disk and survives restarts.

In this guide

* [How a reminder works inside](https://streethosting.com.br/en/guides/discord-bots/discord-reminder-bot#como-funciona)
* [Duration parser: 10m, 2h, 3d](https://streethosting.com.br/en/guides/discord-bots/discord-reminder-bot#parser-duracao)
* [The table and the /lembrete command](https://streethosting.com.br/en/guides/discord-bots/discord-reminder-bot#tabela-comando)
* [Deliver by DM with a channel fallback](https://streethosting.com.br/en/guides/discord-bots/discord-reminder-bot#entrega)
* [Variations worth adding](https://streethosting.com.br/en/guides/discord-bots/discord-reminder-bot#variacoes)
* [Limits to prevent abuse](https://streethosting.com.br/en/guides/discord-bots/discord-reminder-bot#limites)
* [Where to run this on StreetHosting](https://streethosting.com.br/en/guides/discord-bots/discord-reminder-bot#onde-rodar)

## Frequently asked questions

Why not use setTimeout for each reminder?

Because the timer disappears when the bot restarts and because setTimeout accepts at most about 24.8 days. A 3 day reminder would work on paper, but any deploy along the way would wipe it. Storing the date in a database and sweeping from time to time solves both problems.

What happens if the user has DMs closed?

Sending fails with error code 50007. The bot catches the error and sends the reminder in the channel where the command was used, mentioning only that user. If the channel no longer exists either, the reminder is discarded.

What is the shortest interval worth allowing?

One minute is a good minimum. Intervals of seconds invite abuse and the periodic sweep would not be precise enough for them anyway. A reasonable maximum is between 30 and 365 days.

How do I stop someone from flooding the reminder bot?

Limit how many pending reminders each user can have (10 is a common value), limit the text size and turn off automatic mentions in the fallback message, so nobody uses the bot to ping @everyone.

Can I repeat a reminder every day?

Yes. When delivering, instead of deleting the row, add the interval to the due date and keep the reminder. Just add a column that says it repeats and a maximum number of repetitions.

Next step

See bot plans

24/7 Discord bots with fast activation and support in Portuguese.

[See bot plans](https://streethosting.com.br/en/bots)

## Related guides

[Discord bots Intermediate How to schedule automatic Discord messages with a bot Scheduled messages are great for event announcements, voting reminders and recurring notices. See how to implement them with node-schedule in a Node.js bot, plus scheduling best practices. 3 min Read guide](https://streethosting.com.br/en/guides/discord-bots/discord-bot-schedule-messages) [Discord bots Intermediate How to make a Discord bot send automatic DMs to members Automatic DMs are useful for private welcome messages, purchase confirmations and important notifications. Learn how to implement them with discord.js and handle closed DMs correctly. 3 min Read guide](https://streethosting.com.br/en/guides/discord-bots/discord-bot-send-automatic-dm) [Discord bots Intermediate Temporary roles on Discord: how the bot removes them after X days A 30-day VIP or a 7-day timeout only works if the role comes off at the right time, even when the bot restarts along the way. Learn how to store the expiry date and remove the role safely. 8 min Read guide](https://streethosting.com.br/en/guides/discord-bots/discord-temporary-roles-bot)

[← Back to the Guide Center](https://streethosting.com.br/en/guides)
