Telegram

Send and receive Telegram messages in Nuxt: bot token, chat id, photos, inline keyboards, editing, deleting, and updates by webhook instead of long polling.

Telegram is the fastest one to get running and the only one that does everything: send, images, edit, delete, and receive by webhook.

Copy this

Get a token and a chat id

Send /newbot to @BotFather, then write any message to your new bot and open this with your token in it:

https://api.telegram.org/bot<YOUR_TOKEN>/getUpdates

"chat":{"id":123456789 is your chat id.

Configure it

.env
PIGEON_TELEGRAM_BOT_TOKEN=8123456789:AAF...
PIGEON_TELEGRAM_CHAT_ID=123456789
nuxt.config.ts
export default defineNuxtConfig({
  modules: ['nuxt-pigeon'],
  nuxtPigeon: {
    channels: {
      telegram: true,
    },
  },
})

Send

server/api/hello.get.ts
export default defineEventHandler(async () => {
  const message = await telegram.send('<b>Deploy failed</b> on main', {
    parseMode: 'HTML',
  })

  await telegram.edit(message, '<b>Deploy fixed</b>', { parseMode: 'HTML' })

  return { id: message.id }
})

Your phone buzzes, then the message changes in place.

Every option

OptionWhat it does
parseMode'HTML' or 'MarkdownV2'. Without it the text is literal
chatIdAnother chat than the configured one. Any id, nothing has to be declared
mediaOne image, or several as an album. A url is handed over untouched
disableNotificationDelivers silently
disableLinkPreviewNo preview card under a link
replyToMessageIdMakes it a reply
replyMarkupInline keyboard, passed through untouched

Plus timeoutMs, retries, retryDelayMs on every call.

Receiving

nuxt.config.ts
nuxtPigeon: {
  channels: {
    telegram: { receive: true },
  },
}
.env
PIGEON_TELEGRAM_SECRET_TOKEN=any-long-random-string

The secret is yours to invent. Telegram sends it back in a header on every update, and a request without it never reaches your handler.

server/plugins/telegram.ts
export default defineNitroPlugin(() => {
  telegram.listen(async (update) => {
    if (update.message?.text === '/status') {
      await telegram.send('running', { chatId: update.message.chat.id })
    }
  })
})

Register the address once, after starting with --tunnel:

await telegram.setWebhook('https://your-tunnel.trycloudflare.com')

telegram.webhookInfo() tells you what Telegram thinks the address is and what went wrong last, which is the first thing to check when nothing arrives.

What will cost you an hour

  • getUpdates stays empty until you write to the bot. It cannot start a conversation, Telegram forbids it.
  • Group ids are negative, like -1001234567890.
  • In a group the bot only sees messages meant for it. Privacy mode is on by default. Make it an admin, or turn it off with /setprivacy in @BotFather.
  • An image lowers the text limit from 4096 to 1024, because it becomes a caption.
  • You cannot remove an image from a message. There is no deleteMedia and editMessageMedia takes no empty value. Delete and send again.
  • Two pollers at once earn a 409. Not an issue here, the module keeps its listeners in a global symbol and stops them on reload.
  • MarkdownV2 escaping is brutal. Almost every punctuation mark needs a backslash. Use parseMode: 'HTML' with telegram.escapeHtml() and forget it exists.

Read more