---
title: "Comando /lembrete no Discord: bot que avisa por DM depois de 10m, 2h ou 3d"
description: "Crie o comando /lembrete no discord.js v14 com tempo em 10m, 2h ou 3d, salve em banco para sobreviver a reinício e entregue por DM com fallback no canal."
url: "https://streethosting.com.br/guias/bots-discord/bot-discord-lembretes-usuarios"
category: "bots-discord"
slug: "bot-discord-lembretes-usuarios"
datePublished: "2026-11-13"
dateModified: "2026-11-13"
author: "Equipe StreetHosting"
difficulty: "intermediario"
language: "pt-BR"
keywords:
  - "bot discord lembrete comando"
  - "discord bot reminder command"
  - "discord.js lembrete dm"
  - "bot discord agendar aviso usuario"
  - "parser de duração javascript"
---

# Bot de lembretes no Discord: /lembrete com tempo, DM e limite por usuário

Um lembrete só presta se chegar mesmo depois de o bot reiniciar. Veja o parser de duração, a tabela que guarda os avisos, a entrega por DM com plano B no canal e o limite que evita abuso.

> **Resposta rápida**
>
> Converta o texto do usuário (“2h”, “3d”) em milissegundos com um parser, grave no banco a data de vencimento, o usuário, o canal e a mensagem, e rode uma varredura a cada 30 segundos que entrega os vencidos. Envie por DM e, se ela estiver fechada, mande no canal de origem. Como o prazo está no banco, o lembrete sobrevive a qualquer reinício. Limite os lembretes pendentes por usuário para evitar abuso.

## Como o lembrete funciona por dentro

O desenho é o mesmo de qualquer tarefa agendada em bot: separar o que foi pedido do momento de executar. O comando apenas anota um registro com a data em que o aviso deve sair. Uma rotina curta, que roda a cada 30 segundos, consulta o que já venceu, entrega e apaga o registro.

Esse desenho existe porque a alternativa ingênua, um `setTimeout` por lembrete, tem dois defeitos fatais: o temporizador vive na memória e desaparece quando o bot reinicia, e o limite do Node é de 2147483647 milissegundos, cerca de 24,8 dias. O guia de [cargos temporários](https://streethosting.com.br/guias/bots-discord/bot-discord-cargos-temporarios-expiracao) explica em detalhe esses dois problemas, e a solução aqui segue a mesma lógica.

## Parser de duração: 10m, 2h, 3d

O usuário digita “10m”, “2h”, “3d” ou até “1h30m”. O parser precisa aceitar esse formato e rejeitar o resto, porque um texto como “10 banana” não pode virar um lembrete de 10 qualquer coisa:

```
// 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 minuto
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 };
```

A primeira expressão regular garante que a string inteira é uma sequência de número mais unidade, sem lixo no meio. A segunda percorre cada par e soma. O resultado é validado contra o mínimo e o máximo, então “0m”, “99999d” e “abc” devolvem `null`, e o comando responde que o formato é inválido.

## Tabela e o comando /lembrete

Com o SQLite via `better-sqlite3` (`npm install better-sqlite3`), a tabela guarda quem pediu, onde pediu, o texto e o vencimento. Se o seu bot já usa MariaDB, a estrutura é a mesma, com `BIGINT` no lugar do inteiro, `AUTO_INCREMENT` e `VARCHAR(20)` nos IDs, porque o MariaDB não indexa coluna TEXT sem tamanho:

```
// 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 };
```

Agora o comando. Usamos subcomandos: **criar**, **listar** e **cancelar**. O registro e o tratamento de interações estão no guia de [slash commands](https://streethosting.com.br/guias/bots-discord/criar-bot-discord-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))
  );

// no handler de interactionCreate
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,
    });
  }
}
```

Duas escolhas de cuidado aqui. As respostas são efêmeras, então só a pessoa vê o que agendou. E o `cancelar` filtra por `user_id`, o que impede cancelar o lembrete de outra pessoa só chutando o número.

## Entregar por DM com fallback no canal

A varredura busca os vencidos, tenta a DM e, se ela falhar, usa o canal onde o comando foi dado. O erro 50007 quase sempre significa que a pessoa fechou as DMs de membros do servidor, como detalha o guia de [DM automática](https://streethosting.com.br/guias/bots-discord/bot-discord-enviar-mensagem-dm-automatica):

```
// 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 fechada: tenta o canal de origem, mencionando só quem pediu
  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 = canal apagado, 50001/50013 = sem acesso ou sem permissão
    if ([10003, 50001, 50013].includes(err.code)) return true; // sem como entregar, descarta
    console.error('Erro ao enviar lembrete no canal:', err);
    return false; // erro temporário, tenta na próxima rodada
  }
}

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); // recupera o que venceu enquanto o bot estava fora
  setInterval(() => varrer(client), 30 * 1000);
}

module.exports = { iniciarLembretes };
```

Chame `iniciarLembretes(client)` dentro do `Events.ClientReady`. A regra de ouro é só apagar a linha quando a entrega deu certo, ou quando é certo que ela nunca dará. Um erro de rede deixa o lembrete no banco, e ele sai na próxima rodada. Se o bot ficou fora do ar por uma hora, todos os avisos atrasados saem de uma vez ao voltar, o que é melhor do que perder todos. Para um pico grande, o limite de `LIMIT 100` espalha a entrega entre rodadas e evita o [rate limit 429](https://streethosting.com.br/guias/bots-discord/corrigir-rate-limit-discord-429).

> **Dica**
>
> Com varredura a cada 30 segundos, um lembrete de “1m” chega entre 1 e 1,5 minuto depois. Para a maioria dos usos isso é invisível. Se precisar de mais exatidão, calcule o próximo vencimento e agende um `setTimeout` curto (nunca acima de 24 dias) em vez de um intervalo fixo.

## Variações que valem a pena

Com a base pronta, algumas melhorias custam poucas linhas e deixam o comando bem mais útil.

- **Link para a conversa.** Guarde também o ID da mensagem ou da interação e monte o link `https://discord.com/channels/<servidor>/<canal>/<mensagem>`. No lembrete, a pessoa clica e volta ao contexto em que pediu o aviso.
- **Lembrete repetido.** Acrescente uma coluna com o intervalo de repetição. Ao entregar, em vez de apagar, some o intervalo ao vencimento e mantenha a linha. Defina um máximo de repetições, senão um lembrete esquecido continua para sempre.
- **Horário marcado em vez de duração.** “Amanhã às 18h” depende do fuso horário da pessoa, e o Discord não informa esse fuso ao bot. Se quiser esse formato, peça ao usuário para cadastrar o fuso uma vez e converta a hora na hora de gravar. Para o Brasil, lembre que o horário de Brasília é o que vale para a maioria.
- **Adiar o aviso.** Ao entregar por DM, anexe um botão "Lembrar de novo em 10 minutos". O clique cria um novo registro com o mesmo texto.

Cada uma dessas ideias mexe só na tabela e na rotina de entrega. O parser, o comando e a varredura continuam como estão, o que é a vantagem de ter separado o pedido da execução.

## Limites para evitar abuso

Um comando que agenda mensagens e as envia por DM é um alvo natural de abuso. Os limites abaixo custam pouco e fecham as brechas mais comuns:

- **Máximo de pendentes por usuário.** O exemplo usa 10. Quem precisa de mais é raro, e quem tenta lotar a fila é barrado logo.
- **Intervalo mínimo de 1 minuto.** Sem mínimo, alguém agenda lembretes de segundos e transforma o bot em um gerador de DMs.
- **Texto limitado.** 500 caracteres cabem em qualquer anotação e mantêm a mensagem longe do limite de 2000 do Discord, mesmo com o prefixo.
- **Sem menções amplas.** O `allowedMentions` do código impede que um texto com @everyone ou um cargo dispare notificação para o servidor todo.
- **Cooldown no comando.** Se o seu servidor é grande, um intervalo de alguns segundos entre usos do mesmo usuário reduz o spam.

> **Atenção**
>
> Nunca confie no texto do usuário como código ou consulta. Os exemplos usam comandos preparados do SQLite com parâmetros, que evitam injeção de SQL. Não monte a consulta juntando strings.

Os lembretes ficam no seu banco, então faça backup do arquivo. Uma cópia diária do `dados.db` já evita perder os avisos pendentes numa falha de disco. Se quiser mensagens fixas em horário marcado para o servidor inteiro, veja [agendar mensagens no Discord](https://streethosting.com.br/guias/bots-discord/bot-discord-agendar-mensagens-discord).

## Onde rodar na StreetHosting

Bot de lembrete só funciona se estiver ligado quando o prazo vence, o que significa 24 horas por dia. Na [hospedagem de bots da StreetHosting](https://streethosting.com.br/bots) o seu código Node.js roda em um container com painel, e o plano Discord Essential, a R$ 9,99 por mês, traz 1 GB de RAM, 2 GB de NVMe e um banco MariaDB, mais do que suficiente para este bot. O arquivo do SQLite fica no disco do container e sobrevive aos reinícios.

## Perguntas frequentes

### Por que não usar setTimeout para cada lembrete?

Porque o temporizador some quando o bot reinicia e porque o setTimeout aceita no máximo cerca de 24,8 dias. Um lembrete de 3 dias até funcionaria no papel, mas qualquer deploy no meio do caminho o apagaria. Guardar a data em um banco e varrer de tempos em tempos resolve os dois problemas.

### O que acontece se o usuário tiver a DM fechada?

O envio falha com o código de erro 50007. O bot captura o erro e manda o lembrete no canal onde o comando foi usado, mencionando só aquele usuário. Se o canal também não existir mais, o lembrete é descartado.

### Qual o menor intervalo que vale permitir?

Um minuto é um bom mínimo. Intervalos de segundos convidam ao abuso e a varredura periódica nem seria precisa o bastante para eles. O máximo razoável fica entre 30 e 365 dias.

### Como evitar que alguém lote o bot de lembretes?

Limite quantos lembretes pendentes cada usuário pode ter (10 é um valor comum), limite o tamanho do texto e desative as menções automáticas na mensagem de fallback, para que ninguém use o bot para marcar @everyone.

### Dá para repetir um lembrete todo dia?

Sim. Ao entregar, em vez de apagar a linha, some o intervalo à data de vencimento e mantenha o lembrete. Só acrescente uma coluna que indique a repetição e um limite máximo de repetições.

## Guias relacionados

- [Como agendar mensagens automáticas no Discord com bot](https://streethosting.com.br/guias/bots-discord/bot-discord-agendar-mensagens-discord.md)
- [Como fazer o bot Discord enviar DM automática para membros](https://streethosting.com.br/guias/bots-discord/bot-discord-enviar-mensagem-dm-automatica.md)
- [Cargo temporário no Discord: como o bot remove sozinho depois de X dias](https://streethosting.com.br/guias/bots-discord/bot-discord-cargos-temporarios-expiracao.md)
- [Como criar bot Discord com slash commands](https://streethosting.com.br/guias/bots-discord/criar-bot-discord-slash-commands.md)

## Produtos citados

- https://streethosting.com.br/bots

## Dados estruturados

```json
[
  {
    "@context": "https://schema.org",
    "@type": [
      "Article",
      "TechArticle"
    ],
    "headline": "Bot de lembretes no Discord: /lembrete com tempo, DM e limite por usuário",
    "name": "Comando /lembrete no Discord: bot que avisa por DM depois de 10m, 2h ou 3d",
    "abstract": "Um lembrete só presta se chegar mesmo depois de o bot reiniciar. Veja o parser de duração, a tabela que guarda os avisos, a entrega por DM com plano B no canal e o limite que evita abuso.",
    "description": "Crie o comando /lembrete no discord.js v14 com tempo em 10m, 2h ou 3d, salve em banco para sobreviver a reinício e entregue por DM com fallback no canal.",
    "datePublished": "2026-11-13",
    "dateModified": "2026-11-13",
    "author": {
      "@type": "Organization",
      "name": "Equipe StreetHosting"
    },
    "publisher": {
      "@type": "Organization",
      "name": "StreetHosting",
      "url": "https://streethosting.com.br"
    },
    "inLanguage": "pt-BR",
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": "https://streethosting.com.br/guias/bots-discord/bot-discord-lembretes-usuarios"
    }
  },
  {
    "@context": "https://schema.org",
    "@type": "FAQPage",
    "mainEntity": [
      {
        "@type": "Question",
        "name": "Por que não usar setTimeout para cada lembrete?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "Porque o temporizador some quando o bot reinicia e porque o setTimeout aceita no máximo cerca de 24,8 dias. Um lembrete de 3 dias até funcionaria no papel, mas qualquer deploy no meio do caminho o apagaria. Guardar a data em um banco e varrer de tempos em tempos resolve os dois problemas."
        }
      },
      {
        "@type": "Question",
        "name": "O que acontece se o usuário tiver a DM fechada?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "O envio falha com o código de erro 50007. O bot captura o erro e manda o lembrete no canal onde o comando foi usado, mencionando só aquele usuário. Se o canal também não existir mais, o lembrete é descartado."
        }
      },
      {
        "@type": "Question",
        "name": "Qual o menor intervalo que vale permitir?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "Um minuto é um bom mínimo. Intervalos de segundos convidam ao abuso e a varredura periódica nem seria precisa o bastante para eles. O máximo razoável fica entre 30 e 365 dias."
        }
      },
      {
        "@type": "Question",
        "name": "Como evitar que alguém lote o bot de lembretes?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "Limite quantos lembretes pendentes cada usuário pode ter (10 é um valor comum), limite o tamanho do texto e desative as menções automáticas na mensagem de fallback, para que ninguém use o bot para marcar @everyone."
        }
      },
      {
        "@type": "Question",
        "name": "Dá para repetir um lembrete todo dia?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "Sim. Ao entregar, em vez de apagar a linha, some o intervalo à data de vencimento e mantenha o lembrete. Só acrescente uma coluna que indique a repetição e um limite máximo de repetições."
        }
      }
    ]
  }
]
```
